Skip to content

Commit 1cbd099

Browse files
committed
fix(backend): Update Subscription and SubscriptionItem resources
1 parent 9599346 commit 1cbd099

5 files changed

Lines changed: 183 additions & 35 deletions

File tree

‎.changeset/tame-bats-tell.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+
Update fields for BillingSubscription and BillingSubscriptionItem

‎packages/backend/src/api/resources/CommerceSubscription.ts‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type { BillingMoneyAmount } from '@clerk/shared/types';
1+
import type { BillingMoneyAmount, BillingPerUnitTotal, BillingTotals } from '@clerk/shared/types';
22

3+
import { billingMoneyAmountFromJSON, billingPerUnitTotalsFromJSON, billingTotalsFromJSON } from '../../util/billing';
34
import { BillingSubscriptionItem } from './CommerceSubscriptionItem';
45
import type { BillingSubscriptionJSON } from './JSON';
56

@@ -12,6 +13,8 @@ export class BillingSubscription {
1213
constructor(
1314
/** The unique identifier for the Subscription. */
1415
readonly id: string,
16+
/** The ID of the instance this Subscription belongs to. */
17+
readonly instanceId: string,
1518
/** The current status of the Subscription. */
1619
readonly status: BillingSubscriptionJSON['status'],
1720
/** The ID of the payer for this Subscription. */
@@ -32,6 +35,10 @@ export class BillingSubscription {
3235
date: number;
3336
/** The amount of the next payment. */
3437
amount: BillingMoneyAmount;
38+
/** The per-unit cost breakdown for the next payment. */
39+
perUnitTotals?: BillingPerUnitTotal[];
40+
/** The full cost breakdown for the next payment. */
41+
totals?: BillingTotals;
3542
} | null,
3643
/** Whether the payer is eligible for a free trial. */
3744
readonly eligibleForFreeTrial: boolean,
@@ -41,17 +48,17 @@ export class BillingSubscription {
4148
const nextPayment = data.next_payment
4249
? {
4350
date: data.next_payment.date,
44-
amount: {
45-
amount: data.next_payment.amount.amount,
46-
amountFormatted: data.next_payment.amount.amount_formatted,
47-
currency: data.next_payment.amount.currency,
48-
currencySymbol: data.next_payment.amount.currency_symbol,
49-
},
51+
amount: billingMoneyAmountFromJSON(data.next_payment.amount),
52+
perUnitTotals: data.next_payment.per_unit_totals
53+
? billingPerUnitTotalsFromJSON(data.next_payment.per_unit_totals)
54+
: undefined,
55+
totals: data.next_payment.totals ? billingTotalsFromJSON(data.next_payment.totals) : undefined,
5056
}
5157
: null;
5258

5359
return new BillingSubscription(
5460
data.id,
61+
data.instance_id,
5562
data.status,
5663
data.payer_id,
5764
data.created_at,
Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
import type { BillingMoneyAmount, BillingMoneyAmountJSON } from '@clerk/shared/types';
1+
import type {
2+
BillingMoneyAmount,
3+
BillingPerUnitTotal,
4+
BillingSubscriptionItemSeats,
5+
BillingTotals,
6+
} from '@clerk/shared/types';
27

38
import { BillingPlan } from './CommercePlan';
9+
import {
10+
billingMoneyAmountFromJSON,
11+
billingPerUnitTotalsFromJSON,
12+
billingSubscriptionItemSeatsFromJSON,
13+
billingTotalsFromJSON,
14+
} from '../../util/billing';
415
import type { BillingSubscriptionItemJSON } from './JSON';
516

617
/**
@@ -12,6 +23,8 @@ export class BillingSubscriptionItem {
1223
constructor(
1324
/** The unique identifier for the Subscription Item. */
1425
readonly id: string,
26+
/** The ID of the instance this Subscription Item belongs to. */
27+
readonly instanceId: string,
1528
/** The status of the Subscription Item. */
1629
readonly status: BillingSubscriptionItemJSON['status'],
1730
/** The period of the Plan associated with this Subscription Item. */
@@ -22,9 +35,13 @@ export class BillingSubscriptionItem {
2235
readonly nextPayment:
2336
| {
2437
/** The amount of the next payment. */
25-
amount: number;
38+
amount: BillingMoneyAmount;
2639
/** The Unix timestamp (milliseconds) of when the next payment is scheduled. */
2740
date: number;
41+
/** The per-unit cost breakdown for the next payment. */
42+
perUnitTotals?: BillingPerUnitTotal[];
43+
/** The full cost breakdown for the next payment. */
44+
totals?: BillingTotals;
2845
}
2946
| null
3047
| undefined,
@@ -34,6 +51,8 @@ export class BillingSubscriptionItem {
3451
readonly plan: BillingPlan | null,
3552
/** The ID of the Plan associated with this Subscription Item. */
3653
readonly planId: string | null,
54+
/** The ID of the price associated with this Subscription Item. */
55+
readonly priceId: string | null,
3756
/** The Unix timestamp (milliseconds) of when the Subscription Item was created. */
3857
readonly createdAt: number,
3958
/** The Unix timestamp (milliseconds) of when the Subscription Item was last updated. */
@@ -49,36 +68,36 @@ export class BillingSubscriptionItem {
4968
/** The ID of the payer for this Subscription Item. */
5069
readonly payerId: string | undefined,
5170
/** Whether this Subscription Item is currently in a free trial period. */
52-
readonly isFreeTrial?: boolean,
71+
readonly isFreeTrial: boolean,
5372
/** The lifetime amount paid for this Subscription Item. */
5473
readonly lifetimePaid?: BillingMoneyAmount,
74+
/** Seat entitlement details for organization subscription items with seat-based billing. */
75+
readonly seats?: BillingSubscriptionItemSeats,
5576
) {}
5677

5778
static fromJSON(data: BillingSubscriptionItemJSON): BillingSubscriptionItem {
58-
function formatAmountJSON(
59-
amount: BillingMoneyAmountJSON | null | undefined,
60-
): BillingMoneyAmount | null | undefined {
61-
if (!amount) {
62-
return amount;
63-
}
64-
65-
return {
66-
amount: amount.amount,
67-
amountFormatted: amount.amount_formatted,
68-
currency: amount.currency,
69-
currencySymbol: amount.currency_symbol,
70-
};
71-
}
79+
const nextPayment = data.next_payment
80+
? {
81+
amount: billingMoneyAmountFromJSON(data.next_payment.amount),
82+
date: data.next_payment.date,
83+
perUnitTotals: data.next_payment.per_unit_totals
84+
? billingPerUnitTotalsFromJSON(data.next_payment.per_unit_totals)
85+
: undefined,
86+
totals: data.next_payment.totals ? billingTotalsFromJSON(data.next_payment.totals) : undefined,
87+
}
88+
: data.next_payment;
7289

7390
return new BillingSubscriptionItem(
7491
data.id,
92+
data.instance_id,
7593
data.status,
7694
data.plan_period,
7795
data.period_start,
78-
data.next_payment,
79-
formatAmountJSON(data.amount) ?? undefined,
96+
nextPayment,
97+
data.amount ? billingMoneyAmountFromJSON(data.amount) : undefined,
8098
data.plan ? BillingPlan.fromJSON(data.plan) : null,
8199
data.plan_id ?? null,
100+
data.price_id ?? null,
82101
data.created_at,
83102
data.updated_at,
84103
data.period_end,
@@ -87,7 +106,8 @@ export class BillingSubscriptionItem {
87106
data.ended_at,
88107
data.payer_id,
89108
data.is_free_trial,
90-
formatAmountJSON(data.lifetime_paid) ?? undefined,
109+
data.lifetime_paid ? billingMoneyAmountFromJSON(data.lifetime_paid) : undefined,
110+
data.seats ? billingSubscriptionItemSeatsFromJSON(data.seats) : undefined,
91111
);
92112
}
93113
}

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import type { LastAuthenticationStrategy, SignUpStatus, VerificationStatus } from '@clerk/shared/types';
1+
import type {
2+
BillingPerUnitTotalJSON,
3+
BillingSubscriptionItemNextPaymentJSON,
4+
BillingSubscriptionItemSeatsJSON,
5+
BillingTotalsJSON as SharedBillingTotalsJSON,
6+
LastAuthenticationStrategy,
7+
SignUpStatus,
8+
VerificationStatus,
9+
} from '@clerk/shared/types';
210

311
import type {
412
ActorTokenStatus,
@@ -1006,25 +1014,25 @@ type BillingSubscriptionItemStatus =
10061014
*/
10071015
export interface BillingSubscriptionItemJSON extends ClerkResourceJSON {
10081016
object: typeof ObjectType.BillingSubscriptionItem;
1017+
instance_id: string;
10091018
status: BillingSubscriptionItemStatus;
10101019
plan_period: 'month' | 'annual';
10111020
payer_id?: string;
1021+
price_id?: string;
10121022
period_start: number;
10131023
period_end: number | null;
1014-
is_free_trial?: boolean;
1024+
is_free_trial: boolean;
10151025
ended_at: number | null;
10161026
created_at: number;
10171027
updated_at: number;
10181028
canceled_at: number | null;
10191029
past_due_at: number | null;
1020-
lifetime_paid: BillingMoneyAmountJSON | null;
1021-
next_payment?: {
1022-
amount: number;
1023-
date: number;
1024-
} | null;
1025-
amount: BillingMoneyAmountJSON;
1030+
lifetime_paid?: BillingMoneyAmountJSON | null;
1031+
next_payment?: BillingSubscriptionItemNextPaymentJSON | null;
1032+
amount?: BillingMoneyAmountJSON | null;
10261033
plan?: BillingPlanJSON | null;
10271034
plan_id?: string | null;
1035+
seats?: BillingSubscriptionItemSeatsJSON;
10281036
}
10291037

10301038
/**
@@ -1127,6 +1135,7 @@ export interface BillingSubscriptionWebhookEventJSON extends ClerkResourceJSON {
11271135

11281136
export interface BillingSubscriptionJSON extends ClerkResourceJSON {
11291137
object: typeof ObjectType.BillingSubscription;
1138+
instance_id: string;
11301139
status: 'active' | 'past_due' | 'canceled' | 'ended' | 'abandoned' | 'incomplete';
11311140
payer_id: string;
11321141
created_at: number;
@@ -1137,6 +1146,8 @@ export interface BillingSubscriptionJSON extends ClerkResourceJSON {
11371146
next_payment?: {
11381147
date: number;
11391148
amount: BillingMoneyAmountJSON;
1149+
per_unit_totals?: BillingPerUnitTotalJSON[];
1150+
totals?: SharedBillingTotalsJSON;
11401151
};
11411152
eligible_for_free_trial?: boolean;
11421153
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type {
2+
BillingCredits,
3+
BillingCreditsJSON,
4+
BillingDiscounts,
5+
BillingDiscountsJSON,
6+
BillingMoneyAmount,
7+
BillingMoneyAmountJSON,
8+
BillingPeriodTotals,
9+
BillingPeriodTotalsJSON,
10+
BillingPerUnitTotal,
11+
BillingPerUnitTotalJSON,
12+
BillingSubscriptionItemSeats,
13+
BillingSubscriptionItemSeatsJSON,
14+
BillingTotals,
15+
BillingTotalsJSON,
16+
} from '@clerk/shared/types';
17+
18+
export const billingMoneyAmountFromJSON = (amount: BillingMoneyAmountJSON): BillingMoneyAmount => ({
19+
amount: amount.amount,
20+
amountFormatted: amount.amount_formatted,
21+
currency: amount.currency,
22+
currencySymbol: amount.currency_symbol,
23+
});
24+
25+
export const billingPerUnitTotalsFromJSON = (perUnitTotals: BillingPerUnitTotalJSON[]): BillingPerUnitTotal[] =>
26+
perUnitTotals.map(perUnitTotal => ({
27+
name: perUnitTotal.name,
28+
blockSize: perUnitTotal.block_size,
29+
tiers: perUnitTotal.tiers.map(tier => ({
30+
quantity: tier.quantity,
31+
feePerBlock: billingMoneyAmountFromJSON(tier.fee_per_block),
32+
total: billingMoneyAmountFromJSON(tier.total),
33+
})),
34+
}));
35+
36+
const billingCreditsFromJSON = (credits: BillingCreditsJSON): BillingCredits => ({
37+
proration: credits.proration
38+
? {
39+
amount: billingMoneyAmountFromJSON(credits.proration.amount),
40+
cycleDaysRemaining: credits.proration.cycle_days_remaining,
41+
cycleDaysTotal: credits.proration.cycle_days_total,
42+
cycleRemainingPercent: credits.proration.cycle_remaining_percent,
43+
}
44+
: null,
45+
payer: credits.payer
46+
? {
47+
remainingBalance: billingMoneyAmountFromJSON(credits.payer.remaining_balance),
48+
appliedAmount: billingMoneyAmountFromJSON(credits.payer.applied_amount),
49+
}
50+
: null,
51+
total: billingMoneyAmountFromJSON(credits.total),
52+
});
53+
54+
const billingDiscountsFromJSON = (discounts: BillingDiscountsJSON): BillingDiscounts => ({
55+
proration: discounts.proration
56+
? {
57+
amount: billingMoneyAmountFromJSON(discounts.proration.amount),
58+
cycleDaysPassed: discounts.proration.cycle_days_passed,
59+
cycleDaysTotal: discounts.proration.cycle_days_total,
60+
cyclePassedPercent: discounts.proration.cycle_passed_percent,
61+
}
62+
: null,
63+
total: billingMoneyAmountFromJSON(discounts.total),
64+
});
65+
66+
const billingPeriodTotalsFromJSON = (totals: BillingPeriodTotalsJSON): BillingPeriodTotals => ({
67+
subtotal: billingMoneyAmountFromJSON(totals.subtotal),
68+
baseFee: billingMoneyAmountFromJSON(totals.base_fee),
69+
taxTotal: billingMoneyAmountFromJSON(totals.tax_total),
70+
grandTotal: billingMoneyAmountFromJSON(totals.grand_total),
71+
perUnitTotals: totals.per_unit_totals ? billingPerUnitTotalsFromJSON(totals.per_unit_totals) : undefined,
72+
});
73+
74+
export const billingTotalsFromJSON = (totals: BillingTotalsJSON): BillingTotals => ({
75+
subtotal: billingMoneyAmountFromJSON(totals.subtotal),
76+
baseFee: totals.base_fee ? billingMoneyAmountFromJSON(totals.base_fee) : null,
77+
taxTotal: billingMoneyAmountFromJSON(totals.tax_total),
78+
grandTotal: billingMoneyAmountFromJSON(totals.grand_total),
79+
totalDueAfterFreeTrial: totals.total_due_after_free_trial
80+
? billingMoneyAmountFromJSON(totals.total_due_after_free_trial)
81+
: totals.total_due_after_free_trial,
82+
credit: totals.credit ? billingMoneyAmountFromJSON(totals.credit) : totals.credit,
83+
credits: totals.credits ? billingCreditsFromJSON(totals.credits) : null,
84+
discounts: totals.discounts ? billingDiscountsFromJSON(totals.discounts) : null,
85+
pastDue: totals.past_due ? billingMoneyAmountFromJSON(totals.past_due) : totals.past_due,
86+
totalDueNow: totals.total_due_now ? billingMoneyAmountFromJSON(totals.total_due_now) : undefined,
87+
perUnitTotals: totals.per_unit_totals ? billingPerUnitTotalsFromJSON(totals.per_unit_totals) : undefined,
88+
totalsDuePerPeriod: totals.totals_due_per_period
89+
? billingPeriodTotalsFromJSON(totals.totals_due_per_period)
90+
: undefined,
91+
totalDuePerPeriod: totals.total_due_per_period ? billingMoneyAmountFromJSON(totals.total_due_per_period) : undefined,
92+
});
93+
94+
export const billingSubscriptionItemSeatsFromJSON = (
95+
seats: BillingSubscriptionItemSeatsJSON,
96+
): BillingSubscriptionItemSeats => ({
97+
quantity: seats.quantity,
98+
tiers: seats.tiers
99+
? seats.tiers.map(tier => ({
100+
quantity: tier.quantity,
101+
feePerBlock: billingMoneyAmountFromJSON(tier.fee_per_block),
102+
total: billingMoneyAmountFromJSON(tier.total),
103+
}))
104+
: undefined,
105+
});

0 commit comments

Comments
 (0)