Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cute-ideas-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
---

Add support for parsing seat-based billing fields from FAPI.
12 changes: 12 additions & 0 deletions packages/clerk-js/src/core/resources/BillingPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
BillingPayerResourceType,
BillingPlanJSON,
BillingPlanResource,
BillingPlanUnitPrice,
} from '@clerk/shared/types';

import { billingMoneyAmountFromJSON } from '@/utils/billing';
Expand All @@ -24,6 +25,7 @@ export class BillingPlan extends BaseResource implements BillingPlanResource {
slug!: string;
avatarUrl: string | null = null;
features!: Feature[];
unitPrices?: BillingPlanUnitPrice[];
freeTrialDays!: number | null;
freeTrialEnabled!: boolean;

Expand Down Expand Up @@ -53,6 +55,16 @@ export class BillingPlan extends BaseResource implements BillingPlanResource {
this.freeTrialDays = this.withDefault(data.free_trial_days, null);
this.freeTrialEnabled = this.withDefault(data.free_trial_enabled, false);
this.features = (data.features || []).map(feature => new Feature(feature));
this.unitPrices = data.unit_prices?.map(unitPrice => ({
name: unitPrice.name,
blockSize: unitPrice.block_size,
tiers: unitPrice.tiers.map(tier => ({
id: tier.id,
startsAtBlock: tier.starts_at_block,
endsAfterBlock: tier.ends_after_block,
feePerBlock: billingMoneyAmountFromJSON(tier.fee_per_block),
})),
}));

return this;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/clerk-js/src/core/resources/BillingSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
BillingMoneyAmount,
BillingSubscriptionItemJSON,
BillingSubscriptionItemResource,
BillingSubscriptionItemSeats,
BillingSubscriptionJSON,
BillingSubscriptionPlanPeriod,
BillingSubscriptionResource,
Expand Down Expand Up @@ -75,6 +76,7 @@ export class BillingSubscriptionItem extends BaseResource implements BillingSubs
credit?: {
amount: BillingMoneyAmount;
};
seats?: BillingSubscriptionItemSeats;
isFreeTrial!: boolean;

constructor(data: BillingSubscriptionItemJSON) {
Expand Down Expand Up @@ -102,6 +104,7 @@ export class BillingSubscriptionItem extends BaseResource implements BillingSubs
this.amount = data.amount ? billingMoneyAmountFromJSON(data.amount) : undefined;
this.credit =
data.credit && data.credit.amount ? { amount: billingMoneyAmountFromJSON(data.credit.amount) } : undefined;
this.seats = data.seats ? { quantity: data.seats.quantity } : undefined;

this.isFreeTrial = this.withDefault(data.is_free_trial, false);
return this;
Expand Down
17 changes: 17 additions & 0 deletions packages/clerk-js/src/utils/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type {
BillingCheckoutTotalsJSON,
BillingMoneyAmount,
BillingMoneyAmountJSON,
BillingPerUnitTotal,
BillingPerUnitTotalJSON,
BillingStatementTotals,
BillingStatementTotalsJSON,
} from '@clerk/shared/types';
Expand All @@ -16,6 +18,18 @@ export const billingMoneyAmountFromJSON = (data: BillingMoneyAmountJSON): Billin
};
};

const billingPerUnitTotalsFromJSON = (data: BillingPerUnitTotalJSON[]): BillingPerUnitTotal[] => {
return data.map(unitTotal => ({
name: unitTotal.name,
blockSize: unitTotal.block_size,
tiers: unitTotal.tiers.map(tier => ({
quantity: tier.quantity,
feePerBlock: billingMoneyAmountFromJSON(tier.fee_per_block),
total: billingMoneyAmountFromJSON(tier.total),
})),
}));
};

export const billingTotalsFromJSON = <T extends BillingStatementTotalsJSON | BillingCheckoutTotalsJSON>(
data: T,
): T extends { total_due_now: BillingMoneyAmountJSON } ? BillingCheckoutTotals : BillingStatementTotals => {
Expand All @@ -31,6 +45,9 @@ export const billingTotalsFromJSON = <T extends BillingStatementTotalsJSON | Bil
if ('credit' in data) {
totals.credit = data.credit ? billingMoneyAmountFromJSON(data.credit) : null;
}
if ('per_unit_totals' in data) {
totals.perUnitTotals = data.per_unit_totals ? billingPerUnitTotalsFromJSON(data.per_unit_totals) : undefined;
}

if ('total_due_now' in data) {
totals.totalDueNow = billingMoneyAmountFromJSON(data.total_due_now);
Expand Down
109 changes: 109 additions & 0 deletions packages/shared/src/types/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ export interface BillingPlanResource extends ClerkResource {
* The Features the Plan offers.
*/
features: FeatureResource[];
/**
* Per-unit pricing tiers for this Plan (for example, seats).
*/
unitPrices?: BillingPlanUnitPrice[];
/**
* The number of days of the free trial for the Plan. `null` if the Plan does not have a free trial.
*/
Expand All @@ -216,6 +220,102 @@ export interface BillingPlanResource extends ClerkResource {
freeTrialEnabled: boolean;
}

/**
* The `BillingSubscriptionItemSeats` type represents seat entitlements attached to a subscription item.
*
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
export interface BillingSubscriptionItemSeats {
/**
* The seat limit active while the parent subscription item was active. `null` means unlimited.
*/
quantity: number | null;
}

/**
* The `BillingPlanUnitPriceTier` type represents a single pricing tier for a unit type on a plan.
*
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
export interface BillingPlanUnitPriceTier {
/**
* The unique identifier of the unit price tier.
*/
id: string;
/**
* The first block number this tier applies to.
*/
startsAtBlock: number;
/**
* The final block this tier applies to. `null` means unlimited.
*/
endsAfterBlock: number | null;
/**
* The fee charged for each block in this tier.
*/
feePerBlock: BillingMoneyAmount;
}

/**
* The `BillingPlanUnitPrice` type represents unit pricing for a specific unit type (for example, seats) on a plan.
*
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
export interface BillingPlanUnitPrice {
/**
* The unit name, for example `seats`.
*/
name: string;
/**
* Number of units represented by one billable block.
*/
blockSize: number;
/**
* Tiers that define how each block range is priced.
*/
tiers: BillingPlanUnitPriceTier[];
}

/**
* The `BillingPerUnitTotalTier` type represents the cost breakdown for a single tier in checkout totals.
*
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
export interface BillingPerUnitTotalTier {
/**
* The quantity billed within this tier. `null` means unlimited.
*/
quantity: number | null;
/**
* The fee charged per block for this tier.
*/
feePerBlock: BillingMoneyAmount;
/**
* The total billed amount for this tier.
*/
total: BillingMoneyAmount;
}

/**
* The `BillingPerUnitTotal` type represents the per-unit cost breakdown in checkout totals.
*
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
export interface BillingPerUnitTotal {
/**
* The unit name, for example `seats`.
*/
name: string;
/**
* Number of units represented by one billable block.
*/
blockSize: number;
/**
* Detailed tier breakdown for this unit total.
*/
tiers: BillingPerUnitTotalTier[];
}

/**
* The `FeatureResource` type represents a Feature of a Plan.
*
Expand Down Expand Up @@ -593,6 +693,11 @@ export interface BillingSubscriptionItemResource extends ClerkResource {
*/
amount: BillingMoneyAmount;
};
/**
* Seat entitlement details for this subscription item. Only set for organization subscription items with
* seat-based billing.
*/
seats?: BillingSubscriptionItemSeats;
/**
* A function to cancel the subscription item. Accepts the following parameters:
* <ul>
Expand Down Expand Up @@ -708,6 +813,10 @@ export interface BillingCheckoutTotals {
* The amount of tax included in the checkout.
*/
taxTotal: BillingMoneyAmount;
/**
* Per-unit cost breakdown for this checkout (for example, seats).
*/
perUnitTotals?: BillingPerUnitTotal[];
/**
* The amount that needs to be immediately paid to complete the checkout.
*/
Expand Down
75 changes: 75 additions & 0 deletions packages/shared/src/types/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,68 @@ export interface FeatureJSON extends ClerkResourceJSON {
avatar_url: string | null;
}

/**
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
export interface BillingSubscriptionItemSeatsJSON {
/**
* The number of seats available. `null` means unlimited.
*/
quantity: number | null;
}

/**
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*
* Represents a single pricing tier for a unit type on a plan.
*/
export interface BillingPlanUnitPriceTierJSON {
id: string;
object: 'commerce_unit_price';
starts_at_block: number;
/**
* `null` means unlimited.
*/
ends_after_block: number | null;
fee_per_block: BillingMoneyAmountJSON;
}

/**
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*
* Represents unit pricing for a specific unit type (for example, seats) on a plan.
*/
export interface BillingPlanUnitPriceJSON {
name: string;
block_size: number;
tiers: BillingPlanUnitPriceTierJSON[];
}

/**
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*
* Represents the cost breakdown for a single tier in checkout totals.
*/
export interface BillingPerUnitTotalTierJSON {
/**
* `null` means unlimited.
*/
quantity: number | null;
fee_per_block: BillingMoneyAmountJSON;
total: BillingMoneyAmountJSON;
}

/**
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*
* Represents the per-unit cost breakdown in checkout totals.
*/
export interface BillingPerUnitTotalJSON {
name: string;
block_size: number;
tiers: BillingPerUnitTotalTierJSON[];
}

/**
* @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](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes.
*/
Expand All @@ -617,6 +679,10 @@ export interface BillingPlanJSON extends ClerkResourceJSON {
features?: FeatureJSON[];
free_trial_days?: number | null;
free_trial_enabled?: boolean;
/**
* Per-unit pricing tiers for this plan (for example, seats).
*/
unit_prices?: BillingPlanUnitPriceJSON[];
}

/**
Expand Down Expand Up @@ -695,6 +761,11 @@ export interface BillingSubscriptionItemJSON extends ClerkResourceJSON {
credit?: {
amount: BillingMoneyAmountJSON;
};
/**
* Seat entitlement details for this subscription item. Only set for organization subscription items with
* seat-based billing.
*/
seats?: BillingSubscriptionItemSeatsJSON;
plan: BillingPlanJSON;
plan_period: BillingSubscriptionPlanPeriod;
status: BillingSubscriptionStatus;
Expand Down Expand Up @@ -751,6 +822,10 @@ export interface BillingCheckoutTotalsJSON {
grand_total: BillingMoneyAmountJSON;
subtotal: BillingMoneyAmountJSON;
tax_total: BillingMoneyAmountJSON;
/**
* Per-unit cost breakdown for this checkout (for example, seats).
*/
per_unit_totals?: BillingPerUnitTotalJSON[];
total_due_now: BillingMoneyAmountJSON;
credit: BillingMoneyAmountJSON | null;
past_due: BillingMoneyAmountJSON | null;
Expand Down