From 62ec081d35dba6755cada7a0ec6fa0ddbdad485b Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 10:32:52 -0400 Subject: [PATCH 1/8] feat: add credits information in the user/org profile Display the C2's remaining payer credit in the OrganizationProfile component. --- .../src/core/modules/billing/namespace.ts | 11 ++ .../core/resources/BillingCreditBalance.ts | 10 ++ .../src/core/resources/BillingSubscription.ts | 2 + .../clerk-js/src/core/resources/internal.ts | 1 + packages/localizations/src/en-US.ts | 6 + packages/shared/src/react/hooks/index.ts | 1 + .../src/react/hooks/useCreditBalance.tsx | 106 ++++++++++++++++++ packages/shared/src/react/stable-keys.ts | 6 + packages/shared/src/types/billing.ts | 33 ++++++ packages/shared/src/types/elementIds.ts | 3 +- packages/shared/src/types/json.ts | 9 ++ packages/shared/src/types/localization.ts | 6 + .../AccountCredits/AccountCredits.tsx | 38 +++++++ .../ui/src/components/AccountCredits/index.ts | 1 + .../OrganizationBillingPage.tsx | 2 + .../components/UserProfile/BillingPage.tsx | 2 + packages/ui/src/contexts/components/Plans.tsx | 10 ++ 17 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 packages/clerk-js/src/core/resources/BillingCreditBalance.ts create mode 100644 packages/shared/src/react/hooks/useCreditBalance.tsx create mode 100644 packages/ui/src/components/AccountCredits/AccountCredits.tsx create mode 100644 packages/ui/src/components/AccountCredits/index.ts diff --git a/packages/clerk-js/src/core/modules/billing/namespace.ts b/packages/clerk-js/src/core/modules/billing/namespace.ts index b85730adc43..844825d809b 100644 --- a/packages/clerk-js/src/core/modules/billing/namespace.ts +++ b/packages/clerk-js/src/core/modules/billing/namespace.ts @@ -1,5 +1,7 @@ import type { BillingCheckoutJSON, + BillingCreditBalanceJSON, + BillingCreditBalanceResource, BillingNamespace, BillingPaymentJSON, BillingPaymentResource, @@ -11,6 +13,7 @@ import type { BillingSubscriptionResource, ClerkPaginatedResponse, CreateCheckoutParams, + GetCreditBalanceParams, GetPaymentAttemptsParams, GetPlansParams, GetStatementsParams, @@ -21,6 +24,7 @@ import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOff import { BaseResource, BillingCheckout, + BillingCreditBalance, BillingPayment, BillingPlan, BillingStatement, @@ -140,4 +144,11 @@ export class Billing implements BillingNamespace { return new BillingCheckout(json); }; + + getCreditBalance = async (params: GetCreditBalanceParams): Promise => { + return await BaseResource._fetch({ + path: Billing.path(`/payers/${params.payerId}/credits`, { orgId: params.orgId }), + method: 'GET', + }).then(res => new BillingCreditBalance(res?.response as unknown as BillingCreditBalanceJSON)); + }; } diff --git a/packages/clerk-js/src/core/resources/BillingCreditBalance.ts b/packages/clerk-js/src/core/resources/BillingCreditBalance.ts new file mode 100644 index 00000000000..1bed084f9fd --- /dev/null +++ b/packages/clerk-js/src/core/resources/BillingCreditBalance.ts @@ -0,0 +1,10 @@ +import type { BillingCreditBalanceJSON, BillingCreditBalanceResource, BillingMoneyAmount } from '@clerk/shared/types'; +import { billingMoneyAmountFromJSON } from '../../utils'; + +export class BillingCreditBalance implements BillingCreditBalanceResource { + balance: BillingMoneyAmount | null; + + constructor(data: BillingCreditBalanceJSON) { + this.balance = data.balance ? billingMoneyAmountFromJSON(data.balance) : null; + } +} diff --git a/packages/clerk-js/src/core/resources/BillingSubscription.ts b/packages/clerk-js/src/core/resources/BillingSubscription.ts index 3ba88bfe442..35aeb5e1041 100644 --- a/packages/clerk-js/src/core/resources/BillingSubscription.ts +++ b/packages/clerk-js/src/core/resources/BillingSubscription.ts @@ -36,6 +36,7 @@ export class BillingSubscription extends BaseResource implements BillingSubscrip nextPayment?: BillingSubscriptionNextPayment | null; subscriptionItems!: BillingSubscriptionItemResource[]; eligibleForFreeTrial!: boolean; + payerId!: string; constructor(data: BillingSubscriptionJSON) { super(); @@ -63,6 +64,7 @@ export class BillingSubscription extends BaseResource implements BillingSubscrip this.subscriptionItems = (data.subscription_items || []).map(item => new BillingSubscriptionItem(item)); this.eligibleForFreeTrial = this.withDefault(data.eligible_for_free_trial, false); + this.payerId = data.payer_id; return this; } } diff --git a/packages/clerk-js/src/core/resources/internal.ts b/packages/clerk-js/src/core/resources/internal.ts index 9ac3efbd232..e0466c6f348 100644 --- a/packages/clerk-js/src/core/resources/internal.ts +++ b/packages/clerk-js/src/core/resources/internal.ts @@ -5,6 +5,7 @@ export * from './Base'; export * from './APIKey'; export * from './AuthConfig'; export * from './BillingCheckout'; +export * from './BillingCreditBalance'; export * from './BillingPayment'; export * from './BillingPaymentMethod'; export * from './BillingPlan'; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index b13d72e4c04..fb23aff27f8 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -898,6 +898,9 @@ export const enUS: LocalizationResource = { badge__manualInvitation: 'No automatic enrollment', badge__unverified: 'Unverified', billingPage: { + accountCreditsSection: { + title: 'Account credits', + }, paymentHistorySection: { empty: 'No payment history', notFound: 'Payment attempt not found', @@ -1779,6 +1782,9 @@ export const enUS: LocalizationResource = { title__codelist: 'Backup codes', }, billingPage: { + accountCreditsSection: { + title: 'Account credits', + }, paymentHistorySection: { empty: 'No payment history', notFound: 'Payment attempt not found', diff --git a/packages/shared/src/react/hooks/index.ts b/packages/shared/src/react/hooks/index.ts index 34b1f771e1b..fbf300214ae 100644 --- a/packages/shared/src/react/hooks/index.ts +++ b/packages/shared/src/react/hooks/index.ts @@ -23,6 +23,7 @@ export { usePaymentMethods as __experimental_usePaymentMethods } from './usePaym export { usePlans as __experimental_usePlans } from './usePlans'; export { useSubscription as __experimental_useSubscription } from './useSubscription'; export { useCheckout as __experimental_useCheckout } from './useCheckout'; +export { useCreditBalance as __experimental_useCreditBalance } from './useCreditBalance'; /** * Internal hooks to be consumed only by `@clerk/clerk-js`. diff --git a/packages/shared/src/react/hooks/useCreditBalance.tsx b/packages/shared/src/react/hooks/useCreditBalance.tsx new file mode 100644 index 00000000000..b997d4b520e --- /dev/null +++ b/packages/shared/src/react/hooks/useCreditBalance.tsx @@ -0,0 +1,106 @@ +import { useCallback, useEffect, useMemo, useRef } from 'react'; + +import { eventMethodCalled } from '../../telemetry/events'; +import type { BillingCreditBalanceResource, ForPayerType } from '../../types'; +import { useAssertWrappedByClerkProvider, useClerkInstanceContext } from '../contexts'; +import { defineKeepPreviousDataFn } from '../query/keep-previous-data'; +import { useClerkQueryClient } from '../query/use-clerk-query-client'; +import { useClerkQuery } from '../query/useQuery'; +import { STABLE_KEYS } from '../stable-keys'; +import { useOrganizationBase } from './base/useOrganizationBase'; +import { useUserBase } from './base/useUserBase'; +import { useBillingIsEnabled } from './useBillingIsEnabled'; +import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; +import { createCacheKeys } from './createCacheKeys'; + +const HOOK_NAME = 'useCreditBalance'; + +export type UseCreditBalanceParams = { + for?: ForPayerType; + payerId?: string; + keepPreviousData?: boolean; + enabled?: boolean; +}; + +export type CreditBalanceResult = { + data: BillingCreditBalanceResource | undefined | null; + error: Error | undefined; + isLoading: boolean; + isFetching: boolean; + revalidate: () => Promise | void; +}; + +/** + * @internal + */ +export function useCreditBalance(params?: UseCreditBalanceParams): CreditBalanceResult { + useAssertWrappedByClerkProvider(HOOK_NAME); + + const clerk = useClerkInstanceContext(); + const user = useUserBase(); + const organization = useOrganizationBase(); + + const billingEnabled = useBillingIsEnabled(params); + + const recordedRef = useRef(false); + useEffect(() => { + if (!recordedRef.current && clerk?.telemetry) { + clerk.telemetry.record(eventMethodCalled(HOOK_NAME)); + recordedRef.current = true; + } + }, [clerk]); + + const keepPreviousData = params?.keepPreviousData ?? false; + const payerId = params?.payerId; + + const [queryClient] = useClerkQueryClient(); + + const { queryKey, invalidationKey, stableKey, authenticated } = useMemo(() => { + const isOrganization = params?.for === 'organization'; + const safeOrgId = isOrganization ? organization?.id : undefined; + + return createCacheKeys({ + stablePrefix: STABLE_KEYS.CREDIT_BALANCE_KEY, + authenticated: true, + tracked: { + userId: user?.id, + orgId: safeOrgId, + payerId, + }, + untracked: { + args: { payerId: payerId!, orgId: safeOrgId }, + }, + }); + }, [user?.id, organization?.id, params?.for, payerId]); + + const queriesEnabled = Boolean(user?.id && billingEnabled && payerId); + useClearQueriesOnSignOut({ + isSignedOut: user === null, + authenticated, + stableKeys: stableKey, + }); + + const query = useClerkQuery({ + queryKey, + queryFn: ({ queryKey }) => { + const obj = queryKey[3]; + return clerk.billing.getCreditBalance(obj.args); + }, + staleTime: 1_000 * 60, + enabled: queriesEnabled, + placeholderData: defineKeepPreviousDataFn(keepPreviousData && queriesEnabled), + }); + + const revalidate = useCallback( + () => queryClient.invalidateQueries({ queryKey: invalidationKey }), + [queryClient, invalidationKey], + ); + + return { + data: query.data, + error: query.error ?? undefined, + isLoading: query.isLoading, + isFetching: query.isFetching, + revalidate, + }; +} diff --git a/packages/shared/src/react/stable-keys.ts b/packages/shared/src/react/stable-keys.ts index eeab3cd627e..faf261e9c70 100644 --- a/packages/shared/src/react/stable-keys.ts +++ b/packages/shared/src/react/stable-keys.ts @@ -33,6 +33,9 @@ const PAYMENT_ATTEMPTS_KEY = 'billing-payment-attempts'; // Keys for `useStatements` const STATEMENTS_KEY = 'billing-statements'; +// Keys for `useCreditBalance` +const CREDIT_BALANCE_KEY = 'billing-credit-balance'; + export const STABLE_KEYS = { // Keys for `useOrganizationList` USER_MEMBERSHIPS_KEY, @@ -60,6 +63,9 @@ export const STABLE_KEYS = { // Keys for `useOAuthConsent` OAUTH_CONSENT_INFO_KEY, + + // Keys for `useCreditBalance` + CREDIT_BALANCE_KEY, } as const; export type ResourceCacheStableKey = (typeof STABLE_KEYS)[keyof typeof STABLE_KEYS]; diff --git a/packages/shared/src/types/billing.ts b/packages/shared/src/types/billing.ts index 60390e1206e..288ac2a180b 100644 --- a/packages/shared/src/types/billing.ts +++ b/packages/shared/src/types/billing.ts @@ -82,6 +82,13 @@ export interface BillingNamespace { * @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. */ startCheckout: (params: CreateCheckoutParams) => Promise; + + /** + * Gets the credit balance for the current payer. + * + * @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. + */ + getCreditBalance: (params: GetCreditBalanceParams) => Promise; } /** @@ -926,8 +933,34 @@ export interface BillingSubscriptionResource extends ClerkResource { * Whether the payer is eligible for a free trial. */ eligibleForFreeTrial: boolean; + + /** + * The ID of the payer for this subscription. + */ + payerId: string; +} + +/** + * @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 BillingCreditBalanceResource { + /** + * The balance of the credit. + */ + balance: BillingMoneyAmount | null; } +export type GetCreditBalanceParams = { + /** + * The ID of the Organization to get the credit balance for. + */ + orgId?: string; + /** + * The ID of the payer to get the credit balance for. + */ + payerId: string; +}; + /** * The `BillingMoneyAmount` type represents a monetary value with currency information. * diff --git a/packages/shared/src/types/elementIds.ts b/packages/shared/src/types/elementIds.ts index 0a05f8c9b57..b685d908db7 100644 --- a/packages/shared/src/types/elementIds.ts +++ b/packages/shared/src/types/elementIds.ts @@ -62,7 +62,8 @@ export type ProfileSectionId = | 'configureAgain' | 'resetSso' | 'testSsoUrl' - | 'testResults'; + | 'testResults' + | 'accountCredits'; export type ProfilePageId = | 'account' | 'security' diff --git a/packages/shared/src/types/json.ts b/packages/shared/src/types/json.ts index 765ca1d6208..b26740ba3ad 100644 --- a/packages/shared/src/types/json.ts +++ b/packages/shared/src/types/json.ts @@ -925,6 +925,15 @@ export interface BillingSubscriptionJSON extends ClerkResourceJSON { past_due_at: number | null; subscription_items: BillingSubscriptionItemJSON[] | null; eligible_for_free_trial: boolean; + payer_id: string; +} + +/** + * @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 BillingCreditBalanceJSON { + object: 'commerce_credit_balance'; + balance: BillingMoneyAmountJSON | null; } /** diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 72d1c1afa0f..22afe4c76b6 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -938,6 +938,9 @@ export type __internal_LocalizationResource = { }; billingPage: { title: LocalizationValue; + accountCreditsSection: { + title: LocalizationValue; + }; start: { headerTitle__payments: LocalizationValue; headerTitle__plans: LocalizationValue; @@ -1211,6 +1214,9 @@ export type __internal_LocalizationResource = { }; billingPage: { title: LocalizationValue; + accountCreditsSection: { + title: LocalizationValue; + }; start: { headerTitle__payments: LocalizationValue; headerTitle__plans: LocalizationValue; diff --git a/packages/ui/src/components/AccountCredits/AccountCredits.tsx b/packages/ui/src/components/AccountCredits/AccountCredits.tsx new file mode 100644 index 00000000000..ea94fab3581 --- /dev/null +++ b/packages/ui/src/components/AccountCredits/AccountCredits.tsx @@ -0,0 +1,38 @@ +import { ProfileSection } from '@/ui/elements/Section'; +import { useCreditBalance, useSubscriberTypeLocalizationRoot } from '../../contexts'; +import { localizationKeys, Text } from '../../customizables'; + +export const AccountCredits = () => { + const { data: creditBalance, isLoading } = useCreditBalance(); + const localizationRoot = useSubscriberTypeLocalizationRoot(); + + // Only show if balance is not null + if (!creditBalance?.balance || isLoading) { + return null; + } + + return ( + ({ + borderTopWidth: t.borderWidths.$normal, + borderTopStyle: t.borderStyles.$solid, + borderTopColor: t.colors.$borderAlpha100, + })} + > + + + + {creditBalance.balance.currencySymbol} + {creditBalance.balance.amountFormatted} + + + + + ); +}; diff --git a/packages/ui/src/components/AccountCredits/index.ts b/packages/ui/src/components/AccountCredits/index.ts new file mode 100644 index 00000000000..1d82c9eb4fd --- /dev/null +++ b/packages/ui/src/components/AccountCredits/index.ts @@ -0,0 +1 @@ +export * from './AccountCredits'; diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx index fe8018cd709..f47d28b8aad 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationBillingPage.tsx @@ -12,6 +12,7 @@ import { PaymentAttemptsList } from '../PaymentAttempts'; import { PaymentMethods } from '../PaymentMethods'; import { StatementsList } from '../Statements'; import { SubscriptionsList } from '../Subscriptions'; +import { AccountCredits } from '../AccountCredits'; const orgTabMap = { 0: 'subscriptions', @@ -74,6 +75,7 @@ const OrganizationBillingPageInternal = withCardStateProvider(() => { has({ permission: 'org:sys_billing:manage' })}> + diff --git a/packages/ui/src/components/UserProfile/BillingPage.tsx b/packages/ui/src/components/UserProfile/BillingPage.tsx index 51976e905de..3d555676142 100644 --- a/packages/ui/src/components/UserProfile/BillingPage.tsx +++ b/packages/ui/src/components/UserProfile/BillingPage.tsx @@ -11,6 +11,7 @@ import { PaymentAttemptsList } from '../PaymentAttempts'; import { PaymentMethods } from '../PaymentMethods'; import { StatementsList } from '../Statements'; import { SubscriptionsList } from '../Subscriptions'; +import { AccountCredits } from '../AccountCredits'; const tabMap = { 0: 'subscriptions', @@ -66,6 +67,7 @@ const BillingPageInternal = withCardStateProvider(() => { )} /> + diff --git a/packages/ui/src/contexts/components/Plans.tsx b/packages/ui/src/contexts/components/Plans.tsx index ad54266b733..582389347b7 100644 --- a/packages/ui/src/contexts/components/Plans.tsx +++ b/packages/ui/src/contexts/components/Plans.tsx @@ -1,4 +1,5 @@ import { + __experimental_useCreditBalance, __experimental_usePaymentAttempts, __experimental_usePaymentMethods, __experimental_usePlans, @@ -93,6 +94,15 @@ export const usePlans = (params?: { mode: 'cache' }) => { }); }; +export const useCreditBalance = () => { + const params = useBillingHookParams(); + const { data: subscription } = useSubscription(); + return __experimental_useCreditBalance({ + ...params, + payerId: subscription?.payerId, + }); +}; + type HandleSelectPlanProps = { plan: BillingPlanResource; planPeriod: BillingSubscriptionPlanPeriod; From 67295511be873ba6edc2a459a4763aba8019754f Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 12:28:29 -0400 Subject: [PATCH 2/8] feat: add the ability for the payer to view their credit history --- .../src/core/modules/billing/namespace.ts | 22 +++ .../src/core/resources/BillingCreditLedger.ts | 37 ++++ .../clerk-js/src/core/resources/internal.ts | 1 + packages/localizations/src/en-US.ts | 14 ++ packages/shared/src/react/hooks/index.ts | 1 + .../src/react/hooks/useCreditHistory.tsx | 102 +++++++++++ packages/shared/src/react/stable-keys.ts | 3 + packages/shared/src/types/billing.ts | 35 ++++ packages/shared/src/types/json.ts | 15 ++ packages/shared/src/types/localization.ts | 14 ++ .../AccountCredits/AccountCredits.tsx | 16 ++ .../AccountCredits/CreditHistoryPage.tsx | 172 ++++++++++++++++++ .../ui/src/components/AccountCredits/index.ts | 1 + .../OrganizationProfileRoutes.tsx | 11 ++ .../UserProfile/UserProfileRoutes.tsx | 11 ++ packages/ui/src/contexts/components/Plans.tsx | 10 + 16 files changed, 465 insertions(+) create mode 100644 packages/clerk-js/src/core/resources/BillingCreditLedger.ts create mode 100644 packages/shared/src/react/hooks/useCreditHistory.tsx create mode 100644 packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx diff --git a/packages/clerk-js/src/core/modules/billing/namespace.ts b/packages/clerk-js/src/core/modules/billing/namespace.ts index 844825d809b..f255f1b6feb 100644 --- a/packages/clerk-js/src/core/modules/billing/namespace.ts +++ b/packages/clerk-js/src/core/modules/billing/namespace.ts @@ -2,6 +2,8 @@ import type { BillingCheckoutJSON, BillingCreditBalanceJSON, BillingCreditBalanceResource, + BillingCreditLedgerJSON, + BillingCreditLedgerResource, BillingNamespace, BillingPaymentJSON, BillingPaymentResource, @@ -14,6 +16,7 @@ import type { ClerkPaginatedResponse, CreateCheckoutParams, GetCreditBalanceParams, + GetCreditHistoryParams, GetPaymentAttemptsParams, GetPlansParams, GetStatementsParams, @@ -25,6 +28,7 @@ import { BaseResource, BillingCheckout, BillingCreditBalance, + BillingCreditLedger, BillingPayment, BillingPlan, BillingStatement, @@ -151,4 +155,22 @@ export class Billing implements BillingNamespace { method: 'GET', }).then(res => new BillingCreditBalance(res?.response as unknown as BillingCreditBalanceJSON)); }; + + getCreditHistory = async ( + params: GetCreditHistoryParams, + ): Promise> => { + return await BaseResource._fetch({ + path: Billing.path(`/payers/${params.payerId}/credits/history`, { orgId: params.orgId }), + method: 'GET', + }).then(res => { + const { data, total_count } = res?.response as unknown as { + data: BillingCreditLedgerJSON[]; + total_count: number; + }; + return { + total_count, + data: data.map(item => new BillingCreditLedger(item)), + }; + }); + }; } diff --git a/packages/clerk-js/src/core/resources/BillingCreditLedger.ts b/packages/clerk-js/src/core/resources/BillingCreditLedger.ts new file mode 100644 index 00000000000..b3c62f37c24 --- /dev/null +++ b/packages/clerk-js/src/core/resources/BillingCreditLedger.ts @@ -0,0 +1,37 @@ +import type { BillingCreditLedgerJSON, BillingCreditLedgerResource } from '@clerk/shared/types'; + +import { unixEpochToDate } from '@/utils/date'; + +import { BaseResource } from './internal'; + +export class BillingCreditLedger extends BaseResource implements BillingCreditLedgerResource { + id!: string; + payerId!: string; + amount!: number; + currency!: string; + sourceType!: string; + sourceId!: string; + note: string | null = null; + createdAt!: Date; + + constructor(data: BillingCreditLedgerJSON) { + super(); + this.fromJSON(data); + } + + protected fromJSON(data: BillingCreditLedgerJSON | null): this { + if (!data) { + return this; + } + + this.id = data.id; + this.payerId = data.payer_id; + this.amount = data.amount; + this.currency = data.currency; + this.sourceType = data.source_type; + this.sourceId = data.source_id; + this.note = data.note ?? null; + this.createdAt = unixEpochToDate(data.created_at); + return this; + } +} diff --git a/packages/clerk-js/src/core/resources/internal.ts b/packages/clerk-js/src/core/resources/internal.ts index e0466c6f348..fc1232779e6 100644 --- a/packages/clerk-js/src/core/resources/internal.ts +++ b/packages/clerk-js/src/core/resources/internal.ts @@ -6,6 +6,7 @@ export * from './APIKey'; export * from './AuthConfig'; export * from './BillingCheckout'; export * from './BillingCreditBalance'; +export * from './BillingCreditLedger'; export * from './BillingPayment'; export * from './BillingPaymentMethod'; export * from './BillingPlan'; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index fb23aff27f8..ed59c3e2cdc 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -900,6 +900,13 @@ export const enUS: LocalizationResource = { billingPage: { accountCreditsSection: { title: 'Account credits', + viewHistory: 'View credit history', + }, + creditHistoryPage: { + title: 'Account credit history', + tableHeader__amount: 'Amount', + tableHeader__date: 'Date', + tableHeader__reason: 'Reason', }, paymentHistorySection: { empty: 'No payment history', @@ -1784,6 +1791,13 @@ export const enUS: LocalizationResource = { billingPage: { accountCreditsSection: { title: 'Account credits', + viewHistory: 'View credit history', + }, + creditHistoryPage: { + title: 'Account credit history', + tableHeader__amount: 'Amount', + tableHeader__date: 'Date', + tableHeader__reason: 'Reason', }, paymentHistorySection: { empty: 'No payment history', diff --git a/packages/shared/src/react/hooks/index.ts b/packages/shared/src/react/hooks/index.ts index fbf300214ae..c97d1d4b2ad 100644 --- a/packages/shared/src/react/hooks/index.ts +++ b/packages/shared/src/react/hooks/index.ts @@ -31,6 +31,7 @@ export { useCreditBalance as __experimental_useCreditBalance } from './useCredit * * These exist here in order to keep React Query implementations in a centralized place. */ +export { __internal_useCreditHistoryQuery } from './useCreditHistory'; export { __internal_useStatementQuery } from './useStatementQuery'; export { __internal_usePlanDetailsQuery } from './usePlanDetailsQuery'; export { __internal_usePaymentAttemptQuery } from './usePaymentAttemptQuery'; diff --git a/packages/shared/src/react/hooks/useCreditHistory.tsx b/packages/shared/src/react/hooks/useCreditHistory.tsx new file mode 100644 index 00000000000..81c1459a9f7 --- /dev/null +++ b/packages/shared/src/react/hooks/useCreditHistory.tsx @@ -0,0 +1,102 @@ +import { useCallback, useEffect, useMemo, useRef } from 'react'; + +import { eventMethodCalled } from '../../telemetry/events'; +import type { BillingCreditLedgerResource, ClerkPaginatedResponse, ForPayerType } from '../../types'; +import { useAssertWrappedByClerkProvider, useClerkInstanceContext } from '../contexts'; +import { useClerkQueryClient } from '../query/use-clerk-query-client'; +import { useClerkQuery } from '../query/useQuery'; +import { INTERNAL_STABLE_KEYS } from '../stable-keys'; +import { useOrganizationBase } from './base/useOrganizationBase'; +import { useUserBase } from './base/useUserBase'; +import { useBillingIsEnabled } from './useBillingIsEnabled'; +import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; +import { createCacheKeys } from './createCacheKeys'; + +const HOOK_NAME = 'useCreditHistory'; + +export type UseCreditHistoryParams = { + for?: ForPayerType; + payerId?: string; + enabled?: boolean; +}; + +export type CreditHistoryResult = { + data: ClerkPaginatedResponse | undefined; + error: Error | undefined; + isLoading: boolean; + isFetching: boolean; + revalidate: () => Promise | void; +}; + +/** + * @internal + */ +export function __internal_useCreditHistoryQuery(params?: UseCreditHistoryParams): CreditHistoryResult { + useAssertWrappedByClerkProvider(HOOK_NAME); + + const clerk = useClerkInstanceContext(); + const user = useUserBase(); + const organization = useOrganizationBase(); + + const billingEnabled = useBillingIsEnabled(params); + + const recordedRef = useRef(false); + useEffect(() => { + if (!recordedRef.current && clerk?.telemetry) { + clerk.telemetry.record(eventMethodCalled(HOOK_NAME)); + recordedRef.current = true; + } + }, [clerk]); + + const payerId = params?.payerId; + + const [queryClient] = useClerkQueryClient(); + + const { queryKey, invalidationKey, stableKey, authenticated } = useMemo(() => { + const isOrganization = params?.for === 'organization'; + const safeOrgId = isOrganization ? organization?.id : undefined; + + return createCacheKeys({ + stablePrefix: INTERNAL_STABLE_KEYS.CREDIT_HISTORY_KEY, + authenticated: true, + tracked: { + userId: user?.id, + orgId: safeOrgId, + payerId, + }, + untracked: { + args: { payerId: payerId!, orgId: safeOrgId }, + }, + }); + }, [user?.id, organization?.id, params?.for, payerId]); + + const queriesEnabled = Boolean(user?.id && billingEnabled && payerId && (params?.enabled ?? true)); + useClearQueriesOnSignOut({ + isSignedOut: user === null, + authenticated, + stableKeys: stableKey, + }); + + const query = useClerkQuery({ + queryKey, + queryFn: ({ queryKey }) => { + const obj = queryKey[3]; + return clerk.billing.getCreditHistory(obj.args); + }, + staleTime: 1_000 * 60, + enabled: queriesEnabled, + }); + + const revalidate = useCallback( + () => queryClient.invalidateQueries({ queryKey: invalidationKey }), + [queryClient, invalidationKey], + ); + + return { + data: query.data, + error: query.error ?? undefined, + isLoading: query.isLoading, + isFetching: query.isFetching, + revalidate, + }; +} diff --git a/packages/shared/src/react/stable-keys.ts b/packages/shared/src/react/stable-keys.ts index faf261e9c70..6d7c6be925c 100644 --- a/packages/shared/src/react/stable-keys.ts +++ b/packages/shared/src/react/stable-keys.ts @@ -84,10 +84,13 @@ const ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY = 'organizationEnterpriseConnectio const ORGANIZATION_ENTERPRISE_CONNECTION_TEST_RUNS_KEY = 'organizationEnterpriseConnectionTestRuns'; const ORGANIZATION_DOMAINS_KEY = 'organizationDomains'; +const CREDIT_HISTORY_KEY = 'billing-credit-history'; + export const INTERNAL_STABLE_KEYS = { PAYMENT_ATTEMPT_KEY, BILLING_PLANS_KEY, BILLING_STATEMENTS_KEY, + CREDIT_HISTORY_KEY, USER_ENTERPRISE_CONNECTIONS_KEY, ENTERPRISE_CONNECTION_TEST_RUNS_KEY, ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY, diff --git a/packages/shared/src/types/billing.ts b/packages/shared/src/types/billing.ts index 288ac2a180b..0aec4b5ebd9 100644 --- a/packages/shared/src/types/billing.ts +++ b/packages/shared/src/types/billing.ts @@ -89,6 +89,13 @@ export interface BillingNamespace { * @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. */ getCreditBalance: (params: GetCreditBalanceParams) => Promise; + + /** + * Gets the credit history for the current payer. + * + * @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. + */ + getCreditHistory: (params: GetCreditHistoryParams) => Promise>; } /** @@ -961,6 +968,34 @@ export type GetCreditBalanceParams = { payerId: string; }; +/** + * @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 type GetCreditHistoryParams = { + /** + * The ID of the Organization to get the credit history for. + */ + orgId?: string; + /** + * The ID of the payer to get the credit history for. + */ + payerId: string; +}; + +/** + * @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 BillingCreditLedgerResource { + id: string; + payerId: string; + amount: number; + currency: string; + sourceType: string; + sourceId: string; + note: string | null; + createdAt: Date; +} + /** * The `BillingMoneyAmount` type represents a monetary value with currency information. * diff --git a/packages/shared/src/types/json.ts b/packages/shared/src/types/json.ts index b26740ba3ad..1376ded8459 100644 --- a/packages/shared/src/types/json.ts +++ b/packages/shared/src/types/json.ts @@ -936,6 +936,21 @@ export interface BillingCreditBalanceJSON { balance: BillingMoneyAmountJSON | 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 BillingCreditLedgerJSON { + object: 'commerce_credit_ledger'; + id: string; + payer_id: string; + amount: number; + currency: string; + source_type: string; + source_id: string; + note?: string | null; + created_at: number; +} + /** * @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. */ diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 22afe4c76b6..b9901f40689 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -940,6 +940,13 @@ export type __internal_LocalizationResource = { title: LocalizationValue; accountCreditsSection: { title: LocalizationValue; + viewHistory: LocalizationValue; + }; + creditHistoryPage: { + title: LocalizationValue; + tableHeader__amount: LocalizationValue; + tableHeader__date: LocalizationValue; + tableHeader__reason: LocalizationValue; }; start: { headerTitle__payments: LocalizationValue; @@ -1216,6 +1223,13 @@ export type __internal_LocalizationResource = { title: LocalizationValue; accountCreditsSection: { title: LocalizationValue; + viewHistory: LocalizationValue; + }; + creditHistoryPage: { + title: LocalizationValue; + tableHeader__amount: LocalizationValue; + tableHeader__date: LocalizationValue; + tableHeader__reason: LocalizationValue; }; start: { headerTitle__payments: LocalizationValue; diff --git a/packages/ui/src/components/AccountCredits/AccountCredits.tsx b/packages/ui/src/components/AccountCredits/AccountCredits.tsx index ea94fab3581..d1e932eed1f 100644 --- a/packages/ui/src/components/AccountCredits/AccountCredits.tsx +++ b/packages/ui/src/components/AccountCredits/AccountCredits.tsx @@ -1,10 +1,13 @@ import { ProfileSection } from '@/ui/elements/Section'; import { useCreditBalance, useSubscriberTypeLocalizationRoot } from '../../contexts'; import { localizationKeys, Text } from '../../customizables'; +import { ArrowRight } from '../../icons'; +import { useRouter } from '../../router'; export const AccountCredits = () => { const { data: creditBalance, isLoading } = useCreditBalance(); const localizationRoot = useSubscriberTypeLocalizationRoot(); + const { navigate } = useRouter(); // Only show if balance is not null if (!creditBalance?.balance || isLoading) { @@ -33,6 +36,19 @@ export const AccountCredits = () => { + + ({ justifyContent: 'start', height: t.sizes.$8 })]} + rightIcon={ArrowRight} + rightIconSx={t => ({ + width: t.sizes.$4, + height: t.sizes.$4, + })} + onClick={() => void navigate('credit-history')} + /> + ); }; diff --git a/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx b/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx new file mode 100644 index 00000000000..8b66a533fe4 --- /dev/null +++ b/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx @@ -0,0 +1,172 @@ +import { Header } from '@/ui/elements/Header'; +import { ProfileCard } from '@/ui/elements/ProfileCard'; + +import { useCreditBalance, useCreditHistory, useSubscriberTypeLocalizationRoot } from '../../contexts'; +import { Box, descriptors, localizationKeys, Spinner, Text } from '../../customizables'; +import { useRouter } from '../../router'; + +function formatCreditDate(date: Date): string { + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(date); +} + +function formatCreditAmount(amount: number, currencySymbol: string): string { + const absAmount = Math.abs(amount); + const dollars = (absAmount / 100).toFixed(2); + const prefix = amount >= 0 ? '+' : '-'; + return `${prefix}${currencySymbol}${dollars}`; +} + +export const CreditHistoryPage = () => { + const { navigate } = useRouter(); + const localizationRoot = useSubscriberTypeLocalizationRoot(); + const { data: creditBalance } = useCreditBalance(); + const { data: creditHistory, isLoading } = useCreditHistory(); + + const currencySymbol = creditBalance?.balance?.currencySymbol ?? '$'; + + if (isLoading) { + return ( + + + + + + ); + } + + return ( + + ({ + borderBlockEndWidth: t.borderWidths.$normal, + borderBlockEndStyle: t.borderStyles.$solid, + borderBlockEndColor: t.colors.$borderAlpha100, + marginBlockEnd: t.space.$4, + paddingBlockEnd: t.space.$4, + })} + > + void navigate('../')}> + + + + + ({ + borderWidth: t.borderWidths.$normal, + borderStyle: t.borderStyles.$solid, + borderColor: t.colors.$borderAlpha100, + borderRadius: t.radii.$lg, + overflow: 'clip', + })} + > + + + ({ + background: t.colors.$neutralAlpha25, + borderBlockEndWidth: t.borderWidths.$normal, + borderBlockEndStyle: t.borderStyles.$solid, + borderBlockEndColor: t.colors.$borderAlpha100, + })} + > + ({ padding: t.space.$3, textAlign: 'left' })} + > + + + ({ padding: t.space.$3, textAlign: 'left' })} + > + + + ({ padding: t.space.$3, textAlign: 'left' })} + > + + + + + + {creditHistory?.data?.map(entry => ( + ({ + borderBlockEndWidth: t.borderWidths.$normal, + borderBlockEndStyle: t.borderStyles.$solid, + borderBlockEndColor: t.colors.$borderAlpha100, + '&:last-child': { borderBlockEnd: 'none' }, + })} + > + ({ padding: t.space.$3 })} + > + = 0 ? undefined : 'inherit' }} + > + {formatCreditAmount(entry.amount, currencySymbol)} + + + ({ padding: t.space.$3 })} + > + {formatCreditDate(entry.createdAt)} + + ({ padding: t.space.$3 })} + > + + {entry.note || '-'} + + + + ))} + + + + + ); +}; diff --git a/packages/ui/src/components/AccountCredits/index.ts b/packages/ui/src/components/AccountCredits/index.ts index 1d82c9eb4fd..64c3571e3d3 100644 --- a/packages/ui/src/components/AccountCredits/index.ts +++ b/packages/ui/src/components/AccountCredits/index.ts @@ -1 +1,2 @@ export * from './AccountCredits'; +export * from './CreditHistoryPage'; diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx index 72b8f115f43..8ee1198a057 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -37,6 +37,12 @@ const OrganizationPaymentAttemptPage = lazy(() => })), ); +const CreditHistoryPage = lazy(() => + import(/* webpackChunkName: "credit-history-page"*/ '../AccountCredits').then(module => ({ + default: module.CreditHistoryPage, + })), +); + const OrganizationSecurityPage = lazy(() => import(/* webpackChunkName: "op-security-page"*/ './OrganizationSecurityPage').then(module => ({ default: module.OrganizationSecurityPage, @@ -133,6 +139,11 @@ export const OrganizationProfileRoutes = ({ contentRef }: OrganizationProfileRou + + + + + diff --git a/packages/ui/src/components/UserProfile/UserProfileRoutes.tsx b/packages/ui/src/components/UserProfile/UserProfileRoutes.tsx index cf9899370af..fa0d669d06e 100644 --- a/packages/ui/src/components/UserProfile/UserProfileRoutes.tsx +++ b/packages/ui/src/components/UserProfile/UserProfileRoutes.tsx @@ -37,6 +37,12 @@ const PaymentAttemptPage = lazy(() => })), ); +const CreditHistoryPage = lazy(() => + import(/* webpackChunkName: "credit-history-page"*/ '../AccountCredits').then(module => ({ + default: module.CreditHistoryPage, + })), +); + export const UserProfileRoutes = () => { const { pages, shouldShowBilling, apiKeysProps } = useUserProfileContext(); const { apiKeysSettings, commerceSettings } = useEnvironment(); @@ -105,6 +111,11 @@ export const UserProfileRoutes = () => { + + + + + ) : null} diff --git a/packages/ui/src/contexts/components/Plans.tsx b/packages/ui/src/contexts/components/Plans.tsx index 582389347b7..b57e409d21c 100644 --- a/packages/ui/src/contexts/components/Plans.tsx +++ b/packages/ui/src/contexts/components/Plans.tsx @@ -5,6 +5,7 @@ import { __experimental_usePlans, __experimental_useStatements, __experimental_useSubscription, + __internal_useCreditHistoryQuery, __internal_useOrganizationBase, useClerk, useSession, @@ -103,6 +104,15 @@ export const useCreditBalance = () => { }); }; +export const useCreditHistory = () => { + const params = useBillingHookParams(); + const { data: subscription } = useSubscription(); + return __internal_useCreditHistoryQuery({ + ...params, + payerId: subscription?.payerId, + }); +}; + type HandleSelectPlanProps = { plan: BillingPlanResource; planPeriod: BillingSubscriptionPlanPeriod; From b585fd70dd8ab788520fbd8af11240128c22f20b Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 13:22:38 -0400 Subject: [PATCH 3/8] fix: fix the leftIcon lint error --- packages/ui/src/components/AccountCredits/AccountCredits.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/AccountCredits/AccountCredits.tsx b/packages/ui/src/components/AccountCredits/AccountCredits.tsx index d1e932eed1f..eaec2afc578 100644 --- a/packages/ui/src/components/AccountCredits/AccountCredits.tsx +++ b/packages/ui/src/components/AccountCredits/AccountCredits.tsx @@ -35,12 +35,11 @@ export const AccountCredits = () => { {creditBalance.balance.amountFormatted} - - ({ justifyContent: 'start', height: t.sizes.$8 })]} + leftIconSx={() => ({ display: 'none' })} rightIcon={ArrowRight} rightIconSx={t => ({ width: t.sizes.$4, @@ -48,7 +47,7 @@ export const AccountCredits = () => { })} onClick={() => void navigate('credit-history')} /> - + ); }; From d8ae8dbc7ccbc358746f6298e83b55ac386629ec Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 15:00:33 -0400 Subject: [PATCH 4/8] chore: remove reason column from table and increase maxsize in bundle --- .../src/core/resources/BillingCreditLedger.ts | 2 -- packages/localizations/src/en-US.ts | 2 -- packages/shared/src/types/billing.ts | 1 - packages/shared/src/types/json.ts | 1 - packages/shared/src/types/localization.ts | 2 -- .../AccountCredits/CreditHistoryPage.tsx | 23 ------------------- 6 files changed, 31 deletions(-) diff --git a/packages/clerk-js/src/core/resources/BillingCreditLedger.ts b/packages/clerk-js/src/core/resources/BillingCreditLedger.ts index b3c62f37c24..7225c4d3d6b 100644 --- a/packages/clerk-js/src/core/resources/BillingCreditLedger.ts +++ b/packages/clerk-js/src/core/resources/BillingCreditLedger.ts @@ -11,7 +11,6 @@ export class BillingCreditLedger extends BaseResource implements BillingCreditLe currency!: string; sourceType!: string; sourceId!: string; - note: string | null = null; createdAt!: Date; constructor(data: BillingCreditLedgerJSON) { @@ -30,7 +29,6 @@ export class BillingCreditLedger extends BaseResource implements BillingCreditLe this.currency = data.currency; this.sourceType = data.source_type; this.sourceId = data.source_id; - this.note = data.note ?? null; this.createdAt = unixEpochToDate(data.created_at); return this; } diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index ed59c3e2cdc..e3b1fdce85b 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -906,7 +906,6 @@ export const enUS: LocalizationResource = { title: 'Account credit history', tableHeader__amount: 'Amount', tableHeader__date: 'Date', - tableHeader__reason: 'Reason', }, paymentHistorySection: { empty: 'No payment history', @@ -1797,7 +1796,6 @@ export const enUS: LocalizationResource = { title: 'Account credit history', tableHeader__amount: 'Amount', tableHeader__date: 'Date', - tableHeader__reason: 'Reason', }, paymentHistorySection: { empty: 'No payment history', diff --git a/packages/shared/src/types/billing.ts b/packages/shared/src/types/billing.ts index 0aec4b5ebd9..97cf68168e3 100644 --- a/packages/shared/src/types/billing.ts +++ b/packages/shared/src/types/billing.ts @@ -992,7 +992,6 @@ export interface BillingCreditLedgerResource { currency: string; sourceType: string; sourceId: string; - note: string | null; createdAt: Date; } diff --git a/packages/shared/src/types/json.ts b/packages/shared/src/types/json.ts index 1376ded8459..2a4f01ce4f6 100644 --- a/packages/shared/src/types/json.ts +++ b/packages/shared/src/types/json.ts @@ -947,7 +947,6 @@ export interface BillingCreditLedgerJSON { currency: string; source_type: string; source_id: string; - note?: string | null; created_at: number; } diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index b9901f40689..9bce4a89a31 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -946,7 +946,6 @@ export type __internal_LocalizationResource = { title: LocalizationValue; tableHeader__amount: LocalizationValue; tableHeader__date: LocalizationValue; - tableHeader__reason: LocalizationValue; }; start: { headerTitle__payments: LocalizationValue; @@ -1229,7 +1228,6 @@ export type __internal_LocalizationResource = { title: LocalizationValue; tableHeader__amount: LocalizationValue; tableHeader__date: LocalizationValue; - tableHeader__reason: LocalizationValue; }; start: { headerTitle__payments: LocalizationValue; diff --git a/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx b/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx index 8b66a533fe4..980b0689370 100644 --- a/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx +++ b/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx @@ -108,18 +108,6 @@ export const CreditHistoryPage = () => { )} /> - ({ padding: t.space.$3, textAlign: 'left' })} - > - - @@ -151,17 +139,6 @@ export const CreditHistoryPage = () => { > {formatCreditDate(entry.createdAt)} - ({ padding: t.space.$3 })} - > - - {entry.note || '-'} - - ))} From 68ba47e65ed18be84f7ca18ddeaf19f12d82c2f7 Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 15:03:52 -0400 Subject: [PATCH 5/8] fix: increase bundlewatch sizes --- packages/clerk-js/bundlewatch.config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index df17a62a863..2c77b14c39d 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -2,9 +2,9 @@ "files": [ { "path": "./dist/clerk.js", "maxSize": "549KB" }, { "path": "./dist/clerk.browser.js", "maxSize": "74KB" }, - { "path": "./dist/clerk.legacy.browser.js", "maxSize": "114KB" }, + { "path": "./dist/clerk.legacy.browser.js", "maxSize": "115KB" }, { "path": "./dist/clerk.no-rhc.js", "maxSize": "316KB" }, - { "path": "./dist/clerk.native.js", "maxSize": "72KB" }, + { "path": "./dist/clerk.native.js", "maxSize": "73KB" }, { "path": "./dist/vendors*.js", "maxSize": "7KB" }, { "path": "./dist/coinbase*.js", "maxSize": "36KB" }, { "path": "./dist/base-account-sdk*.js", "maxSize": "207KB" }, From e0508886e99297d8b7b064cb8a678e4183dff5bf Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 16:35:26 -0400 Subject: [PATCH 6/8] fix: fix import sort and non-null assertion --- packages/clerk-js/src/core/resources/BillingCreditBalance.ts | 1 + packages/shared/src/react/hooks/useCreditBalance.tsx | 4 ++-- packages/shared/src/react/hooks/useCreditHistory.tsx | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/clerk-js/src/core/resources/BillingCreditBalance.ts b/packages/clerk-js/src/core/resources/BillingCreditBalance.ts index 1bed084f9fd..d63e985929b 100644 --- a/packages/clerk-js/src/core/resources/BillingCreditBalance.ts +++ b/packages/clerk-js/src/core/resources/BillingCreditBalance.ts @@ -1,4 +1,5 @@ import type { BillingCreditBalanceJSON, BillingCreditBalanceResource, BillingMoneyAmount } from '@clerk/shared/types'; + import { billingMoneyAmountFromJSON } from '../../utils'; export class BillingCreditBalance implements BillingCreditBalanceResource { diff --git a/packages/shared/src/react/hooks/useCreditBalance.tsx b/packages/shared/src/react/hooks/useCreditBalance.tsx index b997d4b520e..6cc32538998 100644 --- a/packages/shared/src/react/hooks/useCreditBalance.tsx +++ b/packages/shared/src/react/hooks/useCreditBalance.tsx @@ -9,9 +9,9 @@ import { useClerkQuery } from '../query/useQuery'; import { STABLE_KEYS } from '../stable-keys'; import { useOrganizationBase } from './base/useOrganizationBase'; import { useUserBase } from './base/useUserBase'; +import { createCacheKeys } from './createCacheKeys'; import { useBillingIsEnabled } from './useBillingIsEnabled'; import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; -import { createCacheKeys } from './createCacheKeys'; const HOOK_NAME = 'useCreditBalance'; @@ -68,7 +68,7 @@ export function useCreditBalance(params?: UseCreditBalanceParams): CreditBalance payerId, }, untracked: { - args: { payerId: payerId!, orgId: safeOrgId }, + args: { payerId: payerId as string, orgId: safeOrgId }, }, }); }, [user?.id, organization?.id, params?.for, payerId]); diff --git a/packages/shared/src/react/hooks/useCreditHistory.tsx b/packages/shared/src/react/hooks/useCreditHistory.tsx index 81c1459a9f7..d4202ee44cc 100644 --- a/packages/shared/src/react/hooks/useCreditHistory.tsx +++ b/packages/shared/src/react/hooks/useCreditHistory.tsx @@ -8,9 +8,9 @@ import { useClerkQuery } from '../query/useQuery'; import { INTERNAL_STABLE_KEYS } from '../stable-keys'; import { useOrganizationBase } from './base/useOrganizationBase'; import { useUserBase } from './base/useUserBase'; +import { createCacheKeys } from './createCacheKeys'; import { useBillingIsEnabled } from './useBillingIsEnabled'; import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; -import { createCacheKeys } from './createCacheKeys'; const HOOK_NAME = 'useCreditHistory'; @@ -65,7 +65,7 @@ export function __internal_useCreditHistoryQuery(params?: UseCreditHistoryParams payerId, }, untracked: { - args: { payerId: payerId!, orgId: safeOrgId }, + args: { payerId: payerId as string, orgId: safeOrgId }, }, }); }, [user?.id, organization?.id, params?.for, payerId]); From c1b5a18b14980d798aaae726c7cd5f98f72b50ac Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 17:05:15 -0400 Subject: [PATCH 7/8] feat: add changeset --- .changeset/fancy-rats-stick.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/fancy-rats-stick.md diff --git a/.changeset/fancy-rats-stick.md b/.changeset/fancy-rats-stick.md new file mode 100644 index 00000000000..d6b0124392a --- /dev/null +++ b/.changeset/fancy-rats-stick.md @@ -0,0 +1,8 @@ +--- +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/ui': minor +--- + +Display the C2s Account Credits in the user/org profile. From 697cf415ec54cbae0e2fea45e140bb0640c2ca5b Mon Sep 17 00:00:00 2001 From: l-armstrong Date: Wed, 24 Jun 2026 17:46:54 -0400 Subject: [PATCH 8/8] fix added the explicit JSX.Element return type to CreditHistoryPage --- packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx b/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx index 980b0689370..d0c056db007 100644 --- a/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx +++ b/packages/ui/src/components/AccountCredits/CreditHistoryPage.tsx @@ -20,7 +20,7 @@ function formatCreditAmount(amount: number, currencySymbol: string): string { return `${prefix}${currencySymbol}${dollars}`; } -export const CreditHistoryPage = () => { +export const CreditHistoryPage = (): JSX.Element => { const { navigate } = useRouter(); const localizationRoot = useSubscriberTypeLocalizationRoot(); const { data: creditBalance } = useCreditBalance();