Skip to content
Merged
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()`
2 changes: 1 addition & 1 deletion integration/templates/nuxt-node/pages/pricing-table.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<template>
<experimental_PricingTable />
<PricingTable />
</template>
4 changes: 2 additions & 2 deletions integration/templates/vue-vite/src/views/PricingTable.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { experimental_PricingTable } from '@clerk/vue';
import { PricingTable } from '@clerk/vue';
</script>

<template>
<experimental_PricingTable />
<PricingTable />
</template>
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
22 changes: 11 additions & 11 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@ 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,
__internal_ComponentNavigationContext,
__internal_UserVerificationModalProps,
AuthenticateWithCoinbaseWalletParams,
AuthenticateWithGoogleOneTapParams,
AuthenticateWithMetamaskParams,
AuthenticateWithOKXWalletParams,
CheckoutProps,
Clerk as ClerkInterface,
ClerkAPIError,
ClerkAuthenticateWithWeb3Params,
ClerkOptions,
ClientJSONSnapshot,
ClientResource,
CommerceNamespace,
CreateOrganizationParams,
CreateOrganizationProps,
CredentialReturn,
Expand All @@ -50,6 +49,7 @@ import type {
OrganizationResource,
OrganizationSwitcherProps,
PendingSessionResource,
PlanDetailsProps,
PricingTableProps,
PublicKeyCredentialCreationOptionsWithoutExtensions,
PublicKeyCredentialRequestOptionsWithoutExtensions,
Expand Down Expand Up @@ -131,7 +131,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 @@ -187,7 +187,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 @@ -316,9 +316,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 @@ -546,7 +546,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?: CheckoutProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledBillingFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
Expand Down Expand Up @@ -575,7 +575,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?: PlanDetailsProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledBillingFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
Expand Down Expand Up @@ -1006,7 +1006,7 @@ export class Clerk implements ClerkInterface {
void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node }));
};

public __experimental_mountPricingTable = (node: HTMLDivElement, props?: PricingTableProps): void => {
public mountPricingTable = (node: HTMLDivElement, props?: PricingTableProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledBillingFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
Expand All @@ -1028,7 +1028,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
Loading