Skip to content
Merged
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/moody-corners-wish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/types': patch
---

Display upgrade and downgrade information on `<Checkout />`.
2 changes: 1 addition & 1 deletion packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
{ "path": "./dist/waitlist*.js", "maxSize": "1.3KB" },
{ "path": "./dist/keylessPrompt*.js", "maxSize": "6.5KB" },
{ "path": "./dist/pricingTable*.js", "maxSize": "4.02KB" },
{ "path": "./dist/checkout*.js", "maxSize": "4.92KB" },
{ "path": "./dist/checkout*.js", "maxSize": "5.2KB" },
{ "path": "./dist/paymentSources*.js", "maxSize": "8.5KB" },
{ "path": "./dist/up-billing-page*.js", "maxSize": "2.4KB" },
{ "path": "./dist/op-billing-page*.js", "maxSize": "2.4KB" },
Expand Down
33 changes: 30 additions & 3 deletions packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { SetupIntent } from '@stripe/stripe-js';
import { useMemo, useState } from 'react';

import { useCheckoutContext } from '../../contexts';
import { Box, Button, Col, descriptors, Form, localizationKeys } from '../../customizables';
import { Box, Button, Col, descriptors, Form, localizationKeys, Span } from '../../customizables';
import { Alert, Disclosure, Divider, Drawer, LineItems, Select, SelectButton, SelectOptionList } from '../../elements';
import { useFetch } from '../../hooks';
import { ArrowUpDown } from '../../icons';
Expand All @@ -30,6 +30,9 @@ export const CheckoutForm = ({
}) => {
const { plan, planPeriod, totals } = checkout;

const showAdjustment = totals.proration && totals.totalDueNow.amount > 0;
const showDowngradeInfo = totals.totalDueNow.amount === 0;

return (
<Drawer.Body>
<Box
Expand All @@ -42,14 +45,38 @@ export const CheckoutForm = ({
})}
>
<LineItems.Root>
<LineItems.Group>
{/* TODO(@Commerce): needs localization */}
<Span
localizationKey={'Your features will remain until the end of your current subscription.'}
elementDescriptor={descriptors.lineItemsDowngradeNotice}
sx={t => ({
fontSize: t.fontSizes.$sm,
color: t.colors.$colorTextSecondary,
})}
/>

<LineItems.Group borderTop={showDowngradeInfo}>
<LineItems.Title title={plan.name} />
Copy link
Contributor

Choose a reason for hiding this comment

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

For downgrades, lets show the start date as a description – might need to get this info added to the checkout object
Screenshot 2025-04-28 at 1 25 41 PM

Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be great to show this in the Total Due [Starting date] with the amount they will be charged. But again, i think the checkout create payload will need to update to reflect that.

Copy link
Contributor

Choose a reason for hiding this comment

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

This makes a good case for the difference between grandTotal and totalDueNow?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm waiting on the FAPI endpoints to be updated, I don't know when that will be. I'm using whatever is available at the moment.

{/* TODO(@Commerce): needs localization */}
<LineItems.Description
text={`${plan.currencySymbol} ${planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted}`}
text={`${plan.currencySymbol}${planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted}`}
suffix={`per month${planPeriod === 'annual' ? ', times 12 months' : ''}`}
/>
</LineItems.Group>
{showAdjustment && (
<LineItems.Group>
{/* TODO(@Commerce): needs localization */}
<LineItems.Title
title={'Adjustment'}
description={'Prorated credit for the remainder of your subscription.'}
/>
{/* TODO(@Commerce): needs localization */}
{/* TODO(@Commerce): Replace client-side calculation with server-side calculation once data are available in the response */}
<LineItems.Description
text={`- ${totals.proration?.totalProration.currencySymbol}${((totals.proration?.days || 0) * (totals.proration?.ratePerDay.amount || 0)) / 100}`}
/>
</LineItems.Group>
)}
<LineItems.Group
borderTop
variant='tertiary'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
'lineItemsDescriptionPrefix',
'lineItemsDescriptionText',
'lineItemsCopyButton',
'lineItemsDowngradeNotice',

'actionCard',

Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/ui/elements/LineItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ interface GroupProps {
*/
borderTop?: boolean;
variant?: GroupVariant;
expand?: boolean;
}

function Group({ children, borderTop = false, variant = 'primary' }: GroupProps) {
Expand Down Expand Up @@ -162,7 +163,6 @@ function Description({ text, prefix, suffix, truncateText = false, copyText = fa
sx={t => ({
display: 'inline-flex',
justifyContent: 'flex-end',
alignItems: 'center',
Copy link
Member

Choose a reason for hiding this comment

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

why'd we remove this?

Copy link
Member Author

Choose a reason for hiding this comment

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

to get this
image
and not this
image

gap: t.space.$1,
minWidth: '0',
})}
Expand Down
11 changes: 11 additions & 0 deletions packages/clerk-js/src/utils/commerce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export const commerceTotalsFromJSON = <
// @ts-ignore
totals['totalDueNow'] = commerceMoneyFromJSON(data.total_due_now);
}
if ('proration' in data) {
// @ts-ignore
totals['proration'] = {
// @ts-ignore
days: data.proration.days,
// @ts-ignore
ratePerDay: commerceMoneyFromJSON(data.proration.rate_per_day),
// @ts-ignore
totalProration: commerceMoneyFromJSON(data.proration.total_proration),
};
}

return totals as T extends { total_due_now: __experimental_CommerceMoneyJSON }
? __experimental_CommerceCheckoutTotals
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export type ElementsConfig = {
lineItemsDescriptionSuffix: WithOptions;
lineItemsDescriptionPrefix: WithOptions;
lineItemsCopyButton: WithOptions;
lineItemsDowngradeNotice: WithOptions;

logoBox: WithOptions;
logoImage: WithOptions;
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/commerce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ export interface __experimental_CommerceCheckoutTotals {
grandTotal: __experimental_CommerceMoney;
taxTotal: __experimental_CommerceMoney;
totalDueNow: __experimental_CommerceMoney;
proration?: {
days: number;
ratePerDay: __experimental_CommerceMoney;
totalProration: __experimental_CommerceMoney;
};
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,11 @@ export interface __experimental_CommerceCheckoutTotalsJSON {
subtotal: __experimental_CommerceMoneyJSON;
tax_total: __experimental_CommerceMoneyJSON;
total_due_now: __experimental_CommerceMoneyJSON;
proration?: {
days: number;
rate_per_day: __experimental_CommerceMoneyJSON;
total_proration: __experimental_CommerceMoneyJSON;
};
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
Expand Down