Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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/rich-drinks-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': minor
'@clerk/types': minor
---

[Billing Beta] Replace usage of top level amounts in plan with fees for displaying prices.
4 changes: 2 additions & 2 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/commerce-checkout-totals.mdx",
"types/commerce-feature-json.mdx",
"types/commerce-feature-resource.mdx",
"types/commerce-fee-json.mdx",
"types/commerce-fee.mdx",
"types/commerce-initialized-payment-source-json.mdx",
"types/commerce-initialized-payment-source-resource.mdx",
"types/commerce-money-json.mdx",
"types/commerce-money.mdx",
"types/commerce-payer-resource-type.mdx",
"types/commerce-payment-charge-type.mdx",
"types/commerce-payment-json.mdx",
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/src/api/resources/CommercePlan.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Feature } from './Feature';
import type { CommercePlanJSON } from './JSON';

type CommerceFee = {
type CommerceMoneyAmount = {
amount: number;
amountFormatted: string;
currency: string;
Expand Down Expand Up @@ -53,15 +53,15 @@ export class CommercePlan {
/**
* The monthly fee of the plan.
*/
readonly fee: CommerceFee,
readonly fee: CommerceMoneyAmount,
/**
* The annual fee of the plan.
*/
readonly annualFee: CommerceFee,
readonly annualFee: CommerceMoneyAmount,
/**
* The annual fee of the plan on a monthly basis.
*/
readonly annualMonthlyFee: CommerceFee,
readonly annualMonthlyFee: CommerceMoneyAmount,
/**
* The type of payer for the plan.
*/
Expand Down
18 changes: 9 additions & 9 deletions packages/backend/src/api/resources/JSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,17 +800,17 @@ interface CommercePayeeJSON {
gateway_status: 'active' | 'pending' | 'restricted' | 'disconnected';
}

interface CommerceFeeJSON {
interface CommerceMoneyAmountJSON {
amount: number;
amount_formatted: string;
currency: string;
currency_symbol: string;
}

interface CommerceTotalsJSON {
subtotal: CommerceFeeJSON;
tax_total: CommerceFeeJSON;
grand_total: CommerceFeeJSON;
subtotal: CommerceMoneyAmountJSON;
tax_total: CommerceMoneyAmountJSON;
grand_total: CommerceMoneyAmountJSON;
}

export interface FeatureJSON extends ClerkResourceJSON {
Expand All @@ -836,9 +836,9 @@ export interface CommercePlanJSON extends ClerkResourceJSON {
is_recurring: boolean;
has_base_fee: boolean;
publicly_visible: boolean;
fee: CommerceFeeJSON;
annual_fee: CommerceFeeJSON;
annual_monthly_fee: CommerceFeeJSON;
fee: CommerceMoneyAmountJSON;
annual_fee: CommerceMoneyAmountJSON;
annual_monthly_fee: CommerceMoneyAmountJSON;
for_payer_type: 'org' | 'user';
features: FeatureJSON[];
}
Expand All @@ -847,7 +847,7 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON {
object: typeof ObjectType.CommerceSubscriptionItem;
status: 'abandoned' | 'active' | 'canceled' | 'ended' | 'expired' | 'incomplete' | 'past_due' | 'upcoming';
credit: {
amount: CommerceFeeJSON;
amount: CommerceMoneyAmountJSON;
cycle_days_remaining: number;
cycle_days_total: number;
cycle_remaining_percent: number;
Expand All @@ -861,7 +861,7 @@ export interface CommerceSubscriptionItemJSON extends ClerkResourceJSON {
lifetime_paid: number;
next_payment_amount: number;
next_payment_date: number;
amount: CommerceFeeJSON;
amount: CommerceMoneyAmountJSON;
plan: {
id: string;
instance_id: string;
Expand Down
8 changes: 4 additions & 4 deletions packages/clerk-js/src/core/resources/CommercePayment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {
CommerceMoney,
CommerceMoneyAmount,
CommercePaymentChargeType,
CommercePaymentJSON,
CommercePaymentResource,
Expand All @@ -8,13 +8,13 @@ import type {
CommerceSubscriptionItemResource,
} from '@clerk/types';

import { commerceMoneyFromJSON } from '../../utils';
import { commerceMoneyAmountFromJSON } from '../../utils';
import { unixEpochToDate } from '../../utils/date';
import { BaseResource, CommercePaymentSource, CommerceSubscriptionItem } from './internal';

export class CommercePayment extends BaseResource implements CommercePaymentResource {
id!: string;
amount!: CommerceMoney;
amount!: CommerceMoneyAmount;
failedAt?: Date;
paidAt?: Date;
updatedAt!: Date;
Expand All @@ -38,7 +38,7 @@ export class CommercePayment extends BaseResource implements CommercePaymentReso
}

this.id = data.id;
this.amount = commerceMoneyFromJSON(data.amount);
this.amount = commerceMoneyAmountFromJSON(data.amount);
this.paidAt = data.paid_at ? unixEpochToDate(data.paid_at) : undefined;
this.failedAt = data.failed_at ? unixEpochToDate(data.failed_at) : undefined;
this.updatedAt = unixEpochToDate(data.updated_at);
Expand Down
51 changes: 9 additions & 42 deletions packages/clerk-js/src/core/resources/CommercePlan.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import type {
CommerceMoneyAmount,
CommercePayerResourceType,
CommercePlanJSON,
CommercePlanJSONSnapshot,
CommercePlanResource,
} from '@clerk/types';

import { commerceMoneyAmountFromJSON } from '@/utils/commerce';

import { BaseResource, CommerceFeature } from './internal';

export class CommercePlan extends BaseResource implements CommercePlanResource {
id!: string;
name!: string;
amount!: number;
amountFormatted!: string;
annualAmount!: number;
annualAmountFormatted!: string;
annualMonthlyAmount!: number;
annualMonthlyAmountFormatted!: string;
currencySymbol!: string;
currency!: string;
fee!: CommerceMoneyAmount;
annualFee!: CommerceMoneyAmount;
annualMonthlyFee!: CommerceMoneyAmount;
description!: string;
isDefault!: boolean;
isRecurring!: boolean;
Expand All @@ -40,14 +37,9 @@ export class CommercePlan extends BaseResource implements CommercePlanResource {

this.id = data.id;
this.name = data.name;
this.amount = data.amount;
this.amountFormatted = data.amount_formatted;
this.annualAmount = data.annual_amount;
this.annualAmountFormatted = data.annual_amount_formatted;
this.annualMonthlyAmount = data.annual_monthly_amount;
this.annualMonthlyAmountFormatted = data.annual_monthly_amount_formatted;
this.currencySymbol = data.currency_symbol;
this.currency = data.currency;
this.fee = commerceMoneyAmountFromJSON(data.fee);
this.annualFee = commerceMoneyAmountFromJSON(data.annual_fee);
this.annualMonthlyFee = commerceMoneyAmountFromJSON(data.annual_monthly_fee);
this.description = data.description;
this.isDefault = data.is_default;
this.isRecurring = data.is_recurring;
Expand All @@ -60,29 +52,4 @@ export class CommercePlan extends BaseResource implements CommercePlanResource {

return this;
}

public __internal_toSnapshot(): CommercePlanJSONSnapshot {
return {
object: 'commerce_plan',
id: this.id,
name: this.name,
amount: this.amount,
amount_formatted: this.amountFormatted,
annual_amount: this.annualAmount,
annual_amount_formatted: this.annualAmountFormatted,
annual_monthly_amount: this.annualMonthlyAmount,
annual_monthly_amount_formatted: this.annualMonthlyAmountFormatted,
currency: this.currency,
currency_symbol: this.currencySymbol,
description: this.description,
is_default: this.isDefault,
is_recurring: this.isRecurring,
has_base_fee: this.hasBaseFee,
for_payer_type: this.forPayerType,
publicly_visible: this.publiclyVisible,
slug: this.slug,
avatar_url: this.avatarUrl,
features: this.features.map(feature => feature.__internal_toSnapshot()),
};
}
}
17 changes: 9 additions & 8 deletions packages/clerk-js/src/core/resources/CommerceSubscription.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {
CancelSubscriptionParams,
CommerceMoney,
CommerceMoneyAmount,
CommerceSubscriptionItemJSON,
CommerceSubscriptionItemResource,
CommerceSubscriptionJSON,
Expand All @@ -12,7 +12,7 @@ import type {

import { unixEpochToDate } from '@/utils/date';

import { commerceMoneyFromJSON } from '../../utils';
import { commerceMoneyAmountFromJSON } from '../../utils';
import { BaseResource, CommercePlan, DeletedObject } from './internal';

export class CommerceSubscription extends BaseResource implements CommerceSubscriptionResource {
Expand All @@ -23,7 +23,7 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
pastDueAt!: Date | null;
updatedAt!: Date | null;
nextPayment: {
amount: CommerceMoney;
amount: CommerceMoneyAmount;
date: Date;
} | null = null;
subscriptionItems!: CommerceSubscriptionItemResource[];
Expand All @@ -47,7 +47,7 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
this.pastDueAt = data.past_due_at ? unixEpochToDate(data.past_due_at) : null;
this.nextPayment = data.next_payment
? {
amount: commerceMoneyFromJSON(data.next_payment.amount),
amount: commerceMoneyAmountFromJSON(data.next_payment.amount),
date: unixEpochToDate(data.next_payment.date),
}
: null;
Expand All @@ -69,9 +69,9 @@ export class CommerceSubscriptionItem extends BaseResource implements CommerceSu
canceledAt!: Date | null;
pastDueAt!: Date | null;
//TODO(@COMMERCE): Why can this be undefined ?
amount?: CommerceMoney;
amount?: CommerceMoneyAmount;
credit?: {
amount: CommerceMoney;
amount: CommerceMoneyAmount;
};
isFreeTrial = false;

Expand All @@ -98,8 +98,9 @@ export class CommerceSubscriptionItem extends BaseResource implements CommerceSu
this.periodEnd = data.period_end ? unixEpochToDate(data.period_end) : null;
this.canceledAt = data.canceled_at ? unixEpochToDate(data.canceled_at) : null;

this.amount = data.amount ? commerceMoneyFromJSON(data.amount) : undefined;
this.credit = data.credit && data.credit.amount ? { amount: commerceMoneyFromJSON(data.credit.amount) } : undefined;
this.amount = data.amount ? commerceMoneyAmountFromJSON(data.amount) : undefined;
this.credit =
data.credit && data.credit.amount ? { amount: commerceMoneyAmountFromJSON(data.credit.amount) } : undefined;

this.isFreeTrial = this.withDefault(data.is_free_trial, false);
return this;
Expand Down
8 changes: 5 additions & 3 deletions packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { __experimental_useCheckout as useCheckout, useOrganization } from '@clerk/shared/react';
import type { CommerceMoney, CommercePaymentSourceResource, ConfirmCheckoutParams } from '@clerk/types';
import type { CommerceMoneyAmount, CommercePaymentSourceResource, ConfirmCheckoutParams } from '@clerk/types';
import { useMemo, useState } from 'react';

import { Card } from '@/ui/elements/Card';
Expand Down Expand Up @@ -35,6 +35,8 @@ export const CheckoutForm = withCardStateProvider(() => {
const showPastDue = !!totals.pastDue?.amount && totals.pastDue.amount > 0;
const showDowngradeInfo = !isImmediatePlanChange;

const fee = planPeriod === 'month' ? plan.fee : plan.annualMonthlyFee;

Comment thread
panteliselef marked this conversation as resolved.
return (
<Drawer.Body>
<Box
Expand All @@ -54,7 +56,7 @@ export const CheckoutForm = withCardStateProvider(() => {
/>
<LineItems.Description
prefix={planPeriod === 'annual' ? 'x12' : undefined}
text={`${plan.currencySymbol}${planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted}`}
text={`${fee.currencySymbol}${fee.amountFormatted}`}
suffix={localizationKeys('commerce.checkout.perMonth')}
/>
Comment on lines 58 to 61

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Localize hard-coded 'x12' multiplier

All user-facing strings must be localized. Replace 'x12' with a localization key (e.g., commerce.checkout.x12) or a parameterized key.

-              prefix={planPeriod === 'annual' ? 'x12' : undefined}
+              prefix={planPeriod === 'annual' ? localizationKeys('commerce.checkout.x12') : undefined}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
prefix={planPeriod === 'annual' ? 'x12' : undefined}
text={`${plan.currencySymbol}${planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted}`}
text={`${fee.currencySymbol}${fee.amountFormatted}`}
suffix={localizationKeys('commerce.checkout.perMonth')}
/>
prefix={planPeriod === 'annual' ? localizationKeys('commerce.checkout.x12') : undefined}
text={`${fee.currencySymbol}${fee.amountFormatted}`}
suffix={localizationKeys('commerce.checkout.perMonth')}
/>
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx around lines 58
to 61, the hard-coded string 'x12' used as a prefix should be replaced with a
localized string. Update the code to use a localization key such as
'commerce.checkout.x12' or a parameterized localization key instead of the
literal 'x12' to ensure all user-facing strings are properly localized.

</LineItems.Group>
Expand Down Expand Up @@ -312,7 +314,7 @@ const ExistingPaymentSourceForm = withCardStateProvider(
totalDueNow,
paymentSources,
}: {
totalDueNow: CommerceMoney;
totalDueNow: CommerceMoneyAmount;
paymentSources: CommercePaymentSourceResource[];
}) => {
const { checkout } = useCheckout();
Expand Down
Loading
Loading