Skip to content

Commit 037b113

Browse files
feat(clerk-js,types): Add __experiemental_checkoutContinueUrl option (#5807)
Co-authored-by: Bryce Kalow <[email protected]>
1 parent 12ae2c6 commit 037b113

File tree

18 files changed

+93
-6
lines changed

18 files changed

+93
-6
lines changed

.changeset/metal-zebras-jog.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@clerk/tanstack-react-start': patch
3+
'@clerk/react-router': patch
4+
'@clerk/clerk-js': patch
5+
'@clerk/nextjs': patch
6+
'@clerk/clerk-react': patch
7+
'@clerk/remix': patch
8+
'@clerk/types': patch
9+
---
10+
11+
Introduce `checkoutContinueUrl` option.

packages/clerk-js/bundlewatch.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{ "path": "./dist/clerk.browser.js", "maxSize": "68KB" },
55
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" },
66
{ "path": "./dist/clerk.headless*.js", "maxSize": "52KB" },
7-
{ "path": "./dist/ui-common*.js", "maxSize": "102KB" },
7+
{ "path": "./dist/ui-common*.js", "maxSize": "102.5KB" },
88
{ "path": "./dist/vendors*.js", "maxSize": "39KB" },
99
{ "path": "./dist/coinbase*.js", "maxSize": "38KB" },
1010
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },

packages/clerk-js/src/core/clerk.ts

+9
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ const defaultOptions: ClerkOptions = {
175175
signInForceRedirectUrl: undefined,
176176
signUpForceRedirectUrl: undefined,
177177
treatPendingAsSignedOut: true,
178+
__experimental_checkoutContinueUrl: undefined,
178179
};
179180

180181
export class Clerk implements ClerkInterface {
@@ -1377,6 +1378,14 @@ export class Clerk implements ClerkInterface {
13771378
return this.buildUrlWithAuth(this.#options.afterSignOutUrl);
13781379
}
13791380

1381+
public __experimental_buildCheckoutContinueUrl(): string {
1382+
if (!this.#options.__experimental_checkoutContinueUrl) {
1383+
return this.buildAfterSignInUrl();
1384+
}
1385+
1386+
return this.#options.__experimental_checkoutContinueUrl;
1387+
}
1388+
13801389
public buildWaitlistUrl(options?: { initialValues?: Record<string, string> }): string {
13811390
if (!this.environment || !this.environment.displayConfig) {
13821391
return '';

packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import type { __experimental_CommerceCheckoutResource } from '@clerk/types';
22

3+
import { useCheckoutContext } from '../../contexts';
34
import { Box, Button, descriptors, Heading, localizationKeys, Span, Text } from '../../customizables';
45
import { Drawer, LineItems, useDrawerContext } from '../../elements';
56
import { transitionDurationValues, transitionTiming } from '../../foundations/transitions';
7+
import { useRouter } from '../../router';
68
import { animations } from '../../styledSystem';
79
import { formatDate } from '../../utils';
10+
811
const capitalize = (name: string) => name[0].toUpperCase() + name.slice(1);
912

1013
export const CheckoutComplete = ({
@@ -14,9 +17,14 @@ export const CheckoutComplete = ({
1417
checkout: __experimental_CommerceCheckoutResource;
1518
isMotionSafe: boolean;
1619
}) => {
20+
const router = useRouter();
1721
const { setIsOpen } = useDrawerContext();
22+
const { __experimental_checkoutContinueUrl } = useCheckoutContext();
1823

1924
const handleClose = () => {
25+
if (__experimental_checkoutContinueUrl) {
26+
void router.navigate(__experimental_checkoutContinueUrl);
27+
}
2028
if (setIsOpen) {
2129
setIsOpen(false);
2230
}
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1-
import { createContext, useContext } from 'react';
1+
import { useClerk } from '@clerk/shared/react';
2+
import { createContext, useContext, useMemo } from 'react';
23

34
import type { __experimental_CheckoutCtx } from '../../types';
4-
55
export const __experimental_CheckoutContext = createContext<__experimental_CheckoutCtx | null>(null);
66

77
export const useCheckoutContext = () => {
88
const context = useContext(__experimental_CheckoutContext);
9+
const clerk = useClerk();
910

1011
if (!context || context.componentName !== 'Checkout') {
1112
throw new Error('Clerk: useCheckoutContext called outside Checkout.');
1213
}
1314

15+
const checkoutContinueUrl = useMemo(() => {
16+
// When we're rendered via the PricingTable with mode = 'modal' we provide a `portalRoot` value
17+
// we want to keep users within the context of the modal, so we do this to prevent navigating away
18+
if (context.portalRoot) {
19+
return undefined;
20+
}
21+
22+
if (context.__experimental_checkoutContinueUrl) {
23+
return context.__experimental_checkoutContinueUrl;
24+
}
25+
26+
return clerk.__experimental_buildCheckoutContinueUrl?.();
27+
}, [context.portalRoot, context.__experimental_checkoutContinueUrl, clerk]);
28+
1429
const { componentName, ...ctx } = context;
1530

1631
return {
1732
...ctx,
1833
componentName,
34+
__experimental_checkoutContinueUrl: checkoutContinueUrl,
1935
};
2036
};

packages/clerk-js/src/ui/hooks/useCheckout.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useFetch } from './useFetch';
77

88
export const useCheckout = (props: __experimental_CheckoutProps) => {
99
const { planId, planPeriod, subscriberType = 'user' } = props;
10-
const { __experimental_commerce } = useClerk();
10+
const clerk = useClerk();
1111
const { organization } = useOrganization();
1212
const [currentCheckout, setCurrentCheckout] = useState<__experimental_CommerceCheckoutResource | null>(null);
1313

@@ -19,7 +19,7 @@ export const useCheckout = (props: __experimental_CheckoutProps) => {
1919
revalidate,
2020
error: _error,
2121
} = useFetch(
22-
__experimental_commerce?.__experimental_billing.startCheckout,
22+
clerk.__experimental_commerce?.__experimental_billing.startCheckout,
2323
{
2424
planId,
2525
planPeriod,

packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function MountedCheckoutDrawer({
4242
planPeriod={checkoutDrawer.props.planPeriod}
4343
subscriberType={checkoutDrawer.props.subscriberType}
4444
onSubscriptionComplete={checkoutDrawer.props.onSubscriptionComplete}
45+
portalRoot={checkoutDrawer.props.portalRoot}
4546
/>
4647
)}
4748
</LazyDrawerRenderer>

packages/clerk-js/src/ui/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
__experimental_CheckoutContinueUrl,
23
__experimental_CheckoutProps,
34
__experimental_CommerceInvoiceResource,
45
__experimental_CommercePlanResource,
@@ -118,7 +119,7 @@ export type __experimental_PricingTableCtx = __experimental_PricingTableProps &
118119

119120
export type __experimental_CheckoutCtx = __experimental_CheckoutProps & {
120121
componentName: 'Checkout';
121-
};
122+
} & __experimental_CheckoutContinueUrl;
122123

123124
export type __experimental_PaymentSourcesCtx = {
124125
componentName: 'PaymentSources';

packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const mergeNextClerkPropsWithEnv = (props: Omit<NextClerkProviderProps, '
2525
props.signUpFallbackRedirectUrl || process.env.NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL || '',
2626
afterSignInUrl: props.afterSignInUrl || process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL || '',
2727
afterSignUpUrl: props.afterSignUpUrl || process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL || '',
28+
__experimental_checkoutContinueUrl:
29+
props.__experimental_checkoutContinueUrl || process.env.NEXT_PUBLIC_CLERK_CHECKOUT_CONTINUE_URL || '',
2830
telemetry: props.telemetry ?? {
2931
disabled: isTruthy(process.env.NEXT_PUBLIC_CLERK_TELEMETRY_DISABLED),
3032
debug: isTruthy(process.env.NEXT_PUBLIC_CLERK_TELEMETRY_DEBUG),

packages/react-router/src/utils/env.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => {
2525
signUpFallbackRedirectUrl: getValue('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL'),
2626
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
2727
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
28+
__experimental_checkoutContinueUrl: getValue('CLERK_CHECKOUT_CONTINUE_URL'),
2829
};
2930
};

packages/react/src/isomorphicClerk.ts

+9
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,15 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
323323
}
324324
};
325325

326+
__experimental_buildCheckoutContinueUrl = (): string | void => {
327+
const callback = () => this.clerkjs?.__experimental_buildCheckoutContinueUrl() || '';
328+
if (this.clerkjs && this.loaded) {
329+
return callback();
330+
} else {
331+
this.premountMethodCalls.set('__experimental_buildCheckoutContinueUrl', callback);
332+
}
333+
};
334+
326335
buildAfterMultiSessionSingleSignOutUrl = (): string | void => {
327336
const callback = () => this.clerkjs?.buildAfterMultiSessionSingleSignOutUrl() || '';
328337
if (this.clerkjs && this.loaded) {

packages/remix/src/ssr/loadOptions.ts

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderO
4545
overrides.signUpFallbackRedirectUrl || getEnvVariable('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL', context) || '';
4646
const afterSignInUrl = overrides.afterSignInUrl || getEnvVariable('CLERK_AFTER_SIGN_IN_URL', context) || '';
4747
const afterSignUpUrl = overrides.afterSignUpUrl || getEnvVariable('CLERK_AFTER_SIGN_UP_URL', context) || '';
48+
const __experimental_checkoutContinueUrl =
49+
overrides.__experimental_checkoutContinueUrl || getEnvVariable('CLERK_CHECKOUT_CONTINUE_URL', context) || '';
4850

4951
let proxyUrl;
5052
if (!!relativeOrAbsoluteProxyUrl && isProxyUrlRelative(relativeOrAbsoluteProxyUrl)) {
@@ -81,5 +83,6 @@ export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderO
8183
signUpForceRedirectUrl,
8284
signInFallbackRedirectUrl,
8385
signUpFallbackRedirectUrl,
86+
__experimental_checkoutContinueUrl,
8487
};
8588
};

packages/remix/src/ssr/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AuthObject, Organization, Session, User, VerifyTokenOptions } from '@clerk/backend';
22
import type { RequestState } from '@clerk/backend/internal';
33
import type {
4+
__experimental_CheckoutContinueUrl,
45
LegacyRedirectProps,
56
MultiDomainAndOrProxy,
67
SignInFallbackRedirectUrl,
@@ -36,13 +37,15 @@ export type RootAuthLoaderOptions = {
3637
SignInFallbackRedirectUrl &
3738
SignUpForceRedirectUrl &
3839
SignUpFallbackRedirectUrl &
40+
__experimental_CheckoutContinueUrl &
3941
LegacyRedirectProps;
4042

4143
export type RequestStateWithRedirectUrls = RequestState &
4244
SignInForceRedirectUrl &
4345
SignInFallbackRedirectUrl &
4446
SignUpForceRedirectUrl &
4547
SignUpFallbackRedirectUrl &
48+
__experimental_CheckoutContinueUrl &
4649
LegacyRedirectProps;
4750

4851
export type RootAuthLoaderCallback<Options extends RootAuthLoaderOptions> = (

packages/remix/src/ssr/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export function getResponseClerkState(requestState: RequestStateWithRedirectUrls
9494
__signUpForceRedirectUrl: requestState.signUpForceRedirectUrl,
9595
__signInFallbackRedirectUrl: requestState.signInFallbackRedirectUrl,
9696
__signUpFallbackRedirectUrl: requestState.signUpFallbackRedirectUrl,
97+
__experimental_checkoutContinueUrl: requestState.__experimental_checkoutContinueUrl,
9798
__clerk_debug: debugRequestState(requestState),
9899
__clerkJSUrl: getEnvVariable('CLERK_JS', context),
99100
__clerkJSVersion: getEnvVariable('CLERK_JS_VERSION', context),

packages/tanstack-react-start/src/utils/env.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ export const getPublicEnvVariables = (context?: H3EventContext) => {
2121
telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')),
2222
afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'),
2323
afterSignUpUrl: getValue('CLERK_AFTER_SIGN_UP_URL'),
24+
__experimental_checkoutContinueUrl: getValue('CLERK_CHECKOUT_CONTINUE_URL'),
2425
} as const;
2526
};

packages/types/src/clerk.ts

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type { OAuthProvider, OAuthScope } from './oauth';
3030
import type { OrganizationResource } from './organization';
3131
import type { OrganizationCustomRoleKey } from './organizationMembership';
3232
import type {
33+
__experimental_CheckoutContinueUrl,
3334
AfterMultiSessionSingleSignOutUrl,
3435
AfterSignOutUrl,
3536
LegacyRedirectProps,
@@ -565,6 +566,11 @@ export interface Clerk {
565566
*/
566567
buildAfterSignOutUrl(): string;
567568

569+
/**
570+
* Returns the configured checkoutContinueUrl of the instance.
571+
*/
572+
__experimental_buildCheckoutContinueUrl(): string;
573+
568574
/**
569575
* Returns the configured afterMultiSessionSingleSignOutUrl of the instance.
570576
*/
@@ -814,6 +820,7 @@ export type ClerkOptions = PendingSessionOptions &
814820
SignInFallbackRedirectUrl &
815821
SignUpForceRedirectUrl &
816822
SignUpFallbackRedirectUrl &
823+
__experimental_CheckoutContinueUrl &
817824
LegacyRedirectProps &
818825
AfterSignOutUrl &
819826
AfterMultiSessionSingleSignOutUrl & {
@@ -1563,6 +1570,7 @@ export type WaitlistModalProps = WaitlistProps;
15631570
type __experimental_PricingTableDefaultProps = {
15641571
ctaPosition?: 'top' | 'bottom';
15651572
collapseFeatures?: boolean;
1573+
__experimental_checkoutContinueUrl?: string;
15661574
};
15671575

15681576
type __experimental_PricingTableBaseProps = {
@@ -1584,6 +1592,11 @@ export type __experimental_CheckoutProps = {
15841592
onSubscriptionComplete?: () => void;
15851593
portalId?: string;
15861594
portalRoot?: PortalRoot;
1595+
/**
1596+
* Full URL or path to navigate to after checkout is complete and the user clicks the "Continue" button.
1597+
* @default undefined
1598+
*/
1599+
__experimental_checkoutContinueUrl?: string;
15871600
};
15881601

15891602
export type __experimental_PlanDetailsProps = {

packages/types/src/redirects.ts

+7
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,10 @@ export type SignInForceRedirectUrl = {
121121
*/
122122
signInForceRedirectUrl?: string | null;
123123
};
124+
125+
export type __experimental_CheckoutContinueUrl = {
126+
/**
127+
* The URL to navigate to after the user completes the checkout and clicks the "Continue" button.
128+
*/
129+
__experimental_checkoutContinueUrl?: string | null;
130+
};

pnpm-lock.yaml

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)