Skip to content
Closed
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
10 changes: 10 additions & 0 deletions .changeset/better-streets-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@clerk/tanstack-react-start': minor
'@clerk/chrome-extension': minor
'@clerk/react-router': minor
'@clerk/nextjs': minor
'@clerk/clerk-react': minor
'@clerk/clerk-expo': minor
---

Export a new `<PricingTable />` component. This component renders plans for user or organizations and upon selection the end-user is prompted with a checkout form.
14 changes: 14 additions & 0 deletions .changeset/social-tables-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@clerk/tanstack-react-start': minor
'@clerk/chrome-extension': minor
'@clerk/react-router': minor
'@clerk/clerk-js': minor
'@clerk/nextjs': minor
'@clerk/shared': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
'@clerk/clerk-expo': minor
'@clerk/remix': minor
---

Mark commerce apis as stable
21 changes: 21 additions & 0 deletions .changeset/witty-doors-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@clerk/clerk-js': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
---

Expose stable commerce stable apis under `Clerk.commerce`

## Render the pricing table component
- `Clerk.mountPricingTable`
- `Clerk.unmountPricingTable`

## Commerce namespace
- `Clerk.commerce.initializePaymentSource()`
- `Clerk.commerce.addPaymentSource()`
- `Clerk.commerce.getPaymentSources()`
- `Clerk.commerce.billing`
- `Clerk.commerce.billing.getPlans()`
- `Clerk.commerce.billing.getSubscriptions()`
- `Clerk.commerce.billing.getInvoices()`
- `Clerk.commerce.billing.startCheckout()`
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { __experimental_PricingTable } from '@clerk/nextjs';
import { PricingTable } from '@clerk/nextjs';

export default function PricingTable() {
return <__experimental_PricingTable />;
export default function PricingTablePage() {
return <PricingTable />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ exports[`public exports should not include a breaking change 1`] = `
"OrganizationList",
"OrganizationProfile",
"OrganizationSwitcher",
"PricingTable",
"Protect",
"RedirectToCreateOrganization",
"RedirectToOrganizationProfile",
Expand All @@ -30,7 +31,6 @@ exports[`public exports should not include a breaking change 1`] = `
"UserButton",
"UserProfile",
"Waitlist",
"__experimental_PricingTable",
"useAuth",
"useClerk",
"useEmailLink",
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/sandbox/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ void (async () => {
});
},
'/pricing-table': () => {
Clerk.__experimental_mountPricingTable(app, componentControls.pricingTable.getProps() ?? {});
Clerk.mountPricingTable(app, componentControls.pricingTable.getProps() ?? {});
},
'/open-sign-in': () => {
mountOpenSignInButton(app, componentControls.signIn.getProps() ?? {});
Expand Down
24 changes: 12 additions & 12 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import {
import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url';
import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils';
import type {
__experimental_CheckoutProps,
__experimental_CommerceNamespace,
__experimental_PlanDetailsProps,
__experimental_PricingTableProps,
__internal_CheckoutProps,
__internal_ComponentNavigationContext,
__internal_PlanDetailsProps,
__internal_UserVerificationModalProps,
AuthenticateWithCoinbaseWalletParams,
AuthenticateWithGoogleOneTapParams,
Expand All @@ -30,6 +28,7 @@ import type {
ClerkOptions,
ClientJSONSnapshot,
ClientResource,
CommerceNamespace,
CreateOrganizationParams,
CreateOrganizationProps,
CredentialReturn,
Expand All @@ -50,6 +49,7 @@ import type {
OrganizationResource,
OrganizationSwitcherProps,
PendingSessionResource,
PricingTableProps,
PublicKeyCredentialCreationOptionsWithoutExtensions,
PublicKeyCredentialRequestOptionsWithoutExtensions,
PublicKeyCredentialWithAuthenticatorAssertionResponse,
Expand Down Expand Up @@ -130,7 +130,7 @@ import { eventBus, events } from './events';
import type { FapiClient, FapiRequestCallback } from './fapiClient';
import { createFapiClient } from './fapiClient';
import { createClientFromJwt } from './jwt-client';
import { __experimental_Commerce } from './modules/commerce';
import { Commerce } from './modules/commerce';
import {
BaseResource,
Client,
Expand Down Expand Up @@ -185,7 +185,7 @@ export class Clerk implements ClerkInterface {
version: __PKG_VERSION__,
environment: process.env.NODE_ENV || 'production',
};
private static _commerce: __experimental_CommerceNamespace;
private static _commerce: CommerceNamespace;

public client: ClientResource | undefined;
public session: SignedInSessionResource | null | undefined;
Expand Down Expand Up @@ -314,9 +314,9 @@ export class Clerk implements ClerkInterface {
return this.#options.standardBrowser || false;
}

get __experimental_commerce(): __experimental_CommerceNamespace {
get commerce(): CommerceNamespace {
if (!Clerk._commerce) {
Clerk._commerce = new __experimental_Commerce();
Clerk._commerce = new Commerce();
}
return Clerk._commerce;
}
Expand Down Expand Up @@ -544,7 +544,7 @@ export class Clerk implements ClerkInterface {
void this.#componentControls.ensureMounted().then(controls => controls.closeModal('signIn'));
};

public __internal_openCheckout = (props?: __experimental_CheckoutProps): void => {
public __internal_openCheckout = (props?: __internal_CheckoutProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledBillingFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
Expand All @@ -564,7 +564,7 @@ export class Clerk implements ClerkInterface {
void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('checkout'));
};

public __internal_openPlanDetails = (props?: __experimental_PlanDetailsProps): void => {
public __internal_openPlanDetails = (props?: __internal_PlanDetailsProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledBillingFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
Expand Down Expand Up @@ -995,7 +995,7 @@ export class Clerk implements ClerkInterface {
void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node }));
};

public __experimental_mountPricingTable = (node: HTMLDivElement, props?: __experimental_PricingTableProps): void => {
public mountPricingTable = (node: HTMLDivElement, props?: PricingTableProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledBillingFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
Expand All @@ -1017,7 +1017,7 @@ export class Clerk implements ClerkInterface {
this.telemetry?.record(eventPrebuiltComponentMounted('PricingTable', props));
};

public __experimental_unmountPricingTable = (node: HTMLDivElement): void => {
public unmountPricingTable = (node: HTMLDivElement): void => {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted().then(controls =>
controls.unmountComponent({
Expand Down
52 changes: 24 additions & 28 deletions packages/clerk-js/src/core/modules/commerce/Commerce.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
import type {
__experimental_AddPaymentSourceParams,
__experimental_CommerceBillingNamespace,
__experimental_CommerceInitializedPaymentSourceJSON,
__experimental_CommerceNamespace,
__experimental_CommercePaymentSourceJSON,
__experimental_GetPaymentSourcesParams,
__experimental_InitializePaymentSourceParams,
AddPaymentSourceParams,
ClerkPaginatedResponse,
CommerceBillingNamespace,
CommerceInitializedPaymentSourceJSON,
CommerceNamespace,
CommercePaymentSourceJSON,
GetPaymentSourcesParams,
InitializePaymentSourceParams,
} from '@clerk/types';

import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOffsetSearchParams';
import {
__experimental_CommerceInitializedPaymentSource,
__experimental_CommercePaymentSource,
BaseResource,
} from '../../resources/internal';
import { __experimental_CommerceBilling } from './CommerceBilling';
import { BaseResource, CommerceInitializedPaymentSource, CommercePaymentSource } from '../../resources/internal';
import { CommerceBilling } from './CommerceBilling';

export class __experimental_Commerce implements __experimental_CommerceNamespace {
private static _billing: __experimental_CommerceBillingNamespace;
export class Commerce implements CommerceNamespace {
private static _billing: CommerceBillingNamespace;

get __experimental_billing(): __experimental_CommerceBillingNamespace {
if (!__experimental_Commerce._billing) {
__experimental_Commerce._billing = new __experimental_CommerceBilling();
get billing(): CommerceBillingNamespace {
if (!Commerce._billing) {
Commerce._billing = new CommerceBilling();
}
return __experimental_Commerce._billing;
return Commerce._billing;
}

initializePaymentSource = async (params: __experimental_InitializePaymentSourceParams) => {
initializePaymentSource = async (params: InitializePaymentSourceParams) => {
const { orgId, ...rest } = params;
const json = (
await BaseResource._fetch({
Expand All @@ -37,11 +33,11 @@ export class __experimental_Commerce implements __experimental_CommerceNamespace
method: 'POST',
body: rest as any,
})
)?.response as unknown as __experimental_CommerceInitializedPaymentSourceJSON;
return new __experimental_CommerceInitializedPaymentSource(json);
)?.response as unknown as CommerceInitializedPaymentSourceJSON;
return new CommerceInitializedPaymentSource(json);
};

addPaymentSource = async (params: __experimental_AddPaymentSourceParams) => {
addPaymentSource = async (params: AddPaymentSourceParams) => {
const { orgId, ...rest } = params;

const json = (
Expand All @@ -50,11 +46,11 @@ export class __experimental_Commerce implements __experimental_CommerceNamespace
method: 'POST',
body: rest as any,
})
)?.response as unknown as __experimental_CommercePaymentSourceJSON;
return new __experimental_CommercePaymentSource(json);
)?.response as unknown as CommercePaymentSourceJSON;
return new CommercePaymentSource(json);
};

getPaymentSources = async (params: __experimental_GetPaymentSourcesParams) => {
getPaymentSources = async (params: GetPaymentSourcesParams) => {
const { orgId, ...rest } = params;

return await BaseResource._fetch({
Expand All @@ -63,10 +59,10 @@ export class __experimental_Commerce implements __experimental_CommerceNamespace
search: convertPageToOffsetSearchParams(rest),
}).then(res => {
const { data: paymentSources, total_count } =
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommercePaymentSourceJSON>;
res?.response as unknown as ClerkPaginatedResponse<CommercePaymentSourceJSON>;
return {
total_count,
data: paymentSources.map(paymentSource => new __experimental_CommercePaymentSource(paymentSource)),
data: paymentSources.map(paymentSource => new CommercePaymentSource(paymentSource)),
};
});
};
Expand Down
65 changes: 31 additions & 34 deletions packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import type {
__experimental_CommerceBillingNamespace,
__experimental_CommerceCheckoutJSON,
__experimental_CommerceInvoiceJSON,
__experimental_CommerceInvoiceResource,
__experimental_CommercePlanResource,
__experimental_CommerceProductJSON,
__experimental_CommerceSubscriptionJSON,
__experimental_CommerceSubscriptionResource,
__experimental_CreateCheckoutParams,
__experimental_GetInvoicesParams,
__experimental_GetPlansParams,
__experimental_GetSubscriptionsParams,
ClerkPaginatedResponse,
CommerceBillingNamespace,
CommerceCheckoutJSON,
CommerceInvoiceJSON,
CommerceInvoiceResource,
CommercePlanResource,
CommerceProductJSON,
CommerceSubscriptionJSON,
CommerceSubscriptionResource,
CreateCheckoutParams,
GetInvoicesParams,
GetPlansParams,
GetSubscriptionsParams,
} from '@clerk/types';

import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOffsetSearchParams';
import {
__experimental_CommerceCheckout,
__experimental_CommerceInvoice,
__experimental_CommercePlan,
__experimental_CommerceSubscription,
BaseResource,
CommerceCheckout,
CommerceInvoice,
CommercePlan,
CommerceSubscription,
} from '../../resources/internal';

export class __experimental_CommerceBilling implements __experimental_CommerceBillingNamespace {
getPlans = async (params?: __experimental_GetPlansParams): Promise<__experimental_CommercePlanResource[]> => {
export class CommerceBilling implements CommerceBillingNamespace {
getPlans = async (params?: GetPlansParams): Promise<CommercePlanResource[]> => {
const { data: products } = (await BaseResource._fetch({
path: `/commerce/products`,
method: 'GET',
search: { payerType: params?.subscriberType || '' },
})) as unknown as ClerkPaginatedResponse<__experimental_CommerceProductJSON>;
})) as unknown as ClerkPaginatedResponse<CommerceProductJSON>;

const defaultProduct = products.find(product => product.is_default);
return defaultProduct?.plans.map(plan => new __experimental_CommercePlan(plan)) || [];
return defaultProduct?.plans.map(plan => new CommercePlan(plan)) || [];
};

getSubscriptions = async (
params: __experimental_GetSubscriptionsParams,
): Promise<ClerkPaginatedResponse<__experimental_CommerceSubscriptionResource>> => {
params: GetSubscriptionsParams,
): Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>> => {
const { orgId, ...rest } = params;

return await BaseResource._fetch({
Expand All @@ -46,45 +46,42 @@ export class __experimental_CommerceBilling implements __experimental_CommerceBi
search: convertPageToOffsetSearchParams(rest),
}).then(res => {
const { data: subscriptions, total_count } =
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommerceSubscriptionJSON>;
res?.response as unknown as ClerkPaginatedResponse<CommerceSubscriptionJSON>;

return {
total_count,
data: subscriptions.map(subscription => new __experimental_CommerceSubscription(subscription)),
data: subscriptions.map(subscription => new CommerceSubscription(subscription)),
};
});
};

getInvoices = async (
params: __experimental_GetInvoicesParams,
): Promise<ClerkPaginatedResponse<__experimental_CommerceInvoiceResource>> => {
getInvoices = async (params: GetInvoicesParams): Promise<ClerkPaginatedResponse<CommerceInvoiceResource>> => {
const { orgId, ...rest } = params;

return await BaseResource._fetch({
path: orgId ? `/organizations/${orgId}/commerce/invoices` : `/me/commerce/invoices`,
method: 'GET',
search: convertPageToOffsetSearchParams(rest),
}).then(res => {
const { data: invoices, total_count } =
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommerceInvoiceJSON>;
const { data: invoices, total_count } = res?.response as unknown as ClerkPaginatedResponse<CommerceInvoiceJSON>;

return {
total_count,
data: invoices.map(invoice => new __experimental_CommerceInvoice(invoice)),
data: invoices.map(invoice => new CommerceInvoice(invoice)),
};
});
};

startCheckout = async (params: __experimental_CreateCheckoutParams) => {
startCheckout = async (params: CreateCheckoutParams) => {
const { orgId, ...rest } = params;
const json = (
await BaseResource._fetch<__experimental_CommerceCheckoutJSON>({
await BaseResource._fetch<CommerceCheckoutJSON>({
path: orgId ? `/organizations/${orgId}/commerce/checkouts` : `/me/commerce/checkouts`,
method: 'POST',
body: rest as any,
})
)?.response as unknown as __experimental_CommerceCheckoutJSON;
)?.response as unknown as CommerceCheckoutJSON;

return new __experimental_CommerceCheckout(json, orgId);
return new CommerceCheckout(json, orgId);
};
}
Loading