From f7f8dfffe983fa5216a87b022044b83694de2f93 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 26 Aug 2025 19:58:31 +0300 Subject: [PATCH 1/3] feat(clerk-js,types,shared): Infer resource in `checkout.confirm()` --- .changeset/proud-walls-travel.md | 19 +++ .../core/modules/commerce/CommerceBilling.ts | 2 +- .../src/core/resources/CommerceCheckout.ts | 15 +-- .../src/core/resources/CommercePayer.ts | 41 +++++++ .../ui/components/Checkout/CheckoutForm.tsx | 9 +- .../shared/src/react/hooks/useCheckout.ts | 1 + packages/types/src/commerce.ts | 115 +++++++++++++++++- packages/types/src/json.ts | 27 ++++ 8 files changed, 211 insertions(+), 18 deletions(-) create mode 100644 .changeset/proud-walls-travel.md create mode 100644 packages/clerk-js/src/core/resources/CommercePayer.ts diff --git a/.changeset/proud-walls-travel.md b/.changeset/proud-walls-travel.md new file mode 100644 index 00000000000..13098cc826c --- /dev/null +++ b/.changeset/proud-walls-travel.md @@ -0,0 +1,19 @@ +--- +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/types': minor +--- + +[Billing Beta] `checkout.confirm()` now infers the resource id resulting an less repetition and improved DX. + +After +```tsx +const checkout = Clerk.billing.startCheckout({orgId}) +checkout.confirm() // orgId is always implied +``` + +Before +```tsx +const checkout = clerk.billing.startCheckout({orgId}) +checkout.confirm({orgId}) +``` diff --git a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts index e729ee75609..fbbf08e8c5a 100644 --- a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts +++ b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts @@ -154,6 +154,6 @@ export class CommerceBilling implements CommerceBillingNamespace { }) )?.response as unknown as CommerceCheckoutJSON; - return new CommerceCheckout(json, orgId); + return new CommerceCheckout(json); }; } diff --git a/packages/clerk-js/src/core/resources/CommerceCheckout.ts b/packages/clerk-js/src/core/resources/CommerceCheckout.ts index f8da65639a8..57a8cc25c50 100644 --- a/packages/clerk-js/src/core/resources/CommerceCheckout.ts +++ b/packages/clerk-js/src/core/resources/CommerceCheckout.ts @@ -3,6 +3,7 @@ import type { CommerceCheckoutJSON, CommerceCheckoutResource, CommerceCheckoutTotals, + CommercePayerResource, CommerceSubscriptionPlanPeriod, ConfirmCheckoutParams, } from '@clerk/types'; @@ -10,6 +11,7 @@ import type { import { unixEpochToDate } from '@/utils/date'; import { commerceTotalsFromJSON } from '../../utils'; +import { CommercePayer } from './CommercePayer'; import { BaseResource, CommercePaymentSource, CommercePlan, isClerkAPIResponseError } from './internal'; export class CommerceCheckout extends BaseResource implements CommerceCheckoutResource { @@ -24,11 +26,11 @@ export class CommerceCheckout extends BaseResource implements CommerceCheckoutRe totals!: CommerceCheckoutTotals; isImmediatePlanChange!: boolean; freeTrialEndsAt!: Date | null; + payer!: CommercePayerResource; - constructor(data: CommerceCheckoutJSON, orgId?: string) { + constructor(data: CommerceCheckoutJSON) { super(); this.fromJSON(data); - this.pathRoot = orgId ? `/organizations/${orgId}/commerce/checkouts` : `/me/commerce/checkouts`; } protected fromJSON(data: CommerceCheckoutJSON | null): this { @@ -47,22 +49,21 @@ export class CommerceCheckout extends BaseResource implements CommerceCheckoutRe this.totals = commerceTotalsFromJSON(data.totals); this.isImmediatePlanChange = data.is_immediate_plan_change; this.freeTrialEndsAt = data.free_trial_ends_at ? unixEpochToDate(data.free_trial_ends_at) : null; + this.payer = new CommercePayer(data.payer); return this; } confirm = (params: ConfirmCheckoutParams): Promise => { - const { orgId, ...rest } = params; - // Retry confirmation in case of a 500 error // This will retry up to 3 times with an increasing delay // It retries at 2s, 4s, 6s and 8s return retry( () => this._basePatch({ - path: orgId - ? `/organizations/${orgId}/commerce/checkouts/${this.id}/confirm` + path: this.payer.organizationId + ? `/organizations/${this.payer.organizationId}/commerce/checkouts/${this.id}/confirm` : `/me/commerce/checkouts/${this.id}/confirm`, - body: rest as any, + body: params as any, }), { factor: 1.1, diff --git a/packages/clerk-js/src/core/resources/CommercePayer.ts b/packages/clerk-js/src/core/resources/CommercePayer.ts new file mode 100644 index 00000000000..5412e3f7837 --- /dev/null +++ b/packages/clerk-js/src/core/resources/CommercePayer.ts @@ -0,0 +1,41 @@ +import type { CommercePayerJSON, CommercePayerResource } from '@clerk/types'; + +import { unixEpochToDate } from '@/utils/date'; + +import { BaseResource } from './internal'; + +export class CommercePayer extends BaseResource implements CommercePayerResource { + id!: string; + createdAt!: Date; + updatedAt!: Date; + imageUrl!: string | null; + userId?: string; + email?: string; + firstName?: string; + lastName?: string; + organizationId?: string; + organizationName?: string; + + constructor(data: CommercePayerJSON) { + super(); + this.fromJSON(data); + } + + protected fromJSON(data: CommercePayerJSON | null): this { + if (!data) { + return this; + } + + this.id = data.id; + this.createdAt = unixEpochToDate(data.created_at); + this.updatedAt = unixEpochToDate(data.updated_at); + this.imageUrl = data.image_url; + this.userId = data.user_id; + this.email = data.email; + this.firstName = data.first_name; + this.lastName = data.last_name; + this.organizationId = data.organization_id; + this.organizationName = data.organization_name; + return this; + } +} diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx index c6fb9af4baf..188762292c8 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx @@ -1,4 +1,4 @@ -import { __experimental_useCheckout as useCheckout, useOrganization } from '@clerk/shared/react'; +import { __experimental_useCheckout as useCheckout } from '@clerk/shared/react'; import type { CommerceMoneyAmount, CommercePaymentSourceResource, ConfirmCheckoutParams } from '@clerk/types'; import { useMemo, useState } from 'react'; @@ -136,7 +136,6 @@ export const CheckoutForm = withCardStateProvider(() => { }); const useCheckoutMutations = () => { - const { organization } = useOrganization(); const { for: _for, onSubscriptionComplete } = useCheckoutContext(); const { checkout } = useCheckout(); const { id, confirm } = checkout; @@ -150,11 +149,7 @@ const useCheckoutMutations = () => { card.setLoading(); card.setError(undefined); - const { data, error } = await confirm({ - ...params, - // TODO(@COMMERCE): Come back to this, this should not be needed - ...(_for === 'organization' ? { orgId: organization?.id } : {}), - }); + const { data, error } = await confirm(params); if (error) { handleError(error, [], card.setError); diff --git a/packages/shared/src/react/hooks/useCheckout.ts b/packages/shared/src/react/hooks/useCheckout.ts index 736e7fc7535..98f952b1221 100644 --- a/packages/shared/src/react/hooks/useCheckout.ts +++ b/packages/shared/src/react/hooks/useCheckout.ts @@ -113,6 +113,7 @@ export const useCheckout = (options?: Params): __experimental_UseCheckoutReturn plan: null, paymentSource: null, freeTrialEndsAt: null, + payer: null, }; } const { diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts index 41cd93a193e..104105dd861 100644 --- a/packages/types/src/commerce.ts +++ b/packages/types/src/commerce.ts @@ -1356,7 +1356,7 @@ export type CreateCheckoutParams = WithOptionalOrgType<{ * * ``` */ -export type ConfirmCheckoutParams = WithOptionalOrgType< +export type ConfirmCheckoutParams = | { /** * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. @@ -1407,8 +1407,7 @@ export type ConfirmCheckoutParams = WithOptionalOrgType< * ``` */ useTestCard?: boolean; - } ->; + }; /** * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. @@ -1519,4 +1518,114 @@ export interface CommerceCheckoutResource extends ClerkResource { * ``` */ freeTrialEndsAt: Date | 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + payer: CommercePayerResource; +} + +/** + * @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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ +export interface CommercePayerResource extends ClerkResource { + /** + * @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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + createdAt: Date; + /** + * @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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + updatedAt: Date; + /** + * @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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + imageUrl: 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + userId?: 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + email?: 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + firstName?: 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + lastName?: 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + organizationId?: 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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ + organizationName?: string; } diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index d4cfab0cb91..449fd393848 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -887,6 +887,33 @@ export interface CommerceCheckoutJSON extends ClerkResourceJSON { is_immediate_plan_change: boolean; // TODO(@COMMERCE): Remove optional after GA. free_trial_ends_at?: number | null; + payer: CommercePayerJSON; +} + +/** + * @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 the SDK version and the clerk-js version to a specific version to avoid breaking changes. + * @example + * ```tsx + * + * ``` + */ +export interface CommercePayerJSON extends ClerkResourceJSON { + object: 'commerce_payer'; + id: string; + created_at: number; + updated_at: number; + image_url: string | null; + + // User attributes + user_id?: string; + email?: string; + first_name?: string; + last_name?: string; + + // Organization attributes + organization_id?: string; + organization_name?: string; } export interface ApiKeyJSON extends ClerkResourceJSON { From 85334401365569e083cd4e851afedd634165d1f4 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 26 Aug 2025 20:25:07 +0300 Subject: [PATCH 2/3] update typedocs --- .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap index 62b308658bd..2202b67a81c 100644 --- a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap +++ b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap @@ -32,7 +32,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = ` "types/commerce-initialized-payment-source-resource.mdx", "types/commerce-money-amount-json.mdx", "types/commerce-money-amount.mdx", + "types/commerce-payer-json.mdx", "types/commerce-payer-resource-type.mdx", + "types/commerce-payer-resource.mdx", "types/commerce-payment-charge-type.mdx", "types/commerce-payment-json.mdx", "types/commerce-payment-resource.mdx", From 716c3730bc36d4c6f6efbbc2b61b798ee905235c Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 27 Aug 2025 00:10:59 +0300 Subject: [PATCH 3/3] Update .changeset/proud-walls-travel.md Co-authored-by: Robert Soriano --- .changeset/proud-walls-travel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/proud-walls-travel.md b/.changeset/proud-walls-travel.md index 13098cc826c..8e514d6b192 100644 --- a/.changeset/proud-walls-travel.md +++ b/.changeset/proud-walls-travel.md @@ -4,7 +4,7 @@ '@clerk/types': minor --- -[Billing Beta] `checkout.confirm()` now infers the resource id resulting an less repetition and improved DX. +[Billing Beta] `checkout.confirm()` now infers the resource id resulting in less repetition and improved DX. After ```tsx