diff --git a/.changeset/great-moons-occur.md b/.changeset/great-moons-occur.md
new file mode 100644
index 00000000000..c5e7f4ffb75
--- /dev/null
+++ b/.changeset/great-moons-occur.md
@@ -0,0 +1,7 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/shared': patch
+'@clerk/types': patch
+---
+
+Add Payment Sources to ``, hook up all org-related payment source and checkout methods to the org-specific endpoints
diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json
index 3a9f48d6527..b7754135697 100644
--- a/packages/clerk-js/bundlewatch.config.json
+++ b/packages/clerk-js/bundlewatch.config.json
@@ -1,9 +1,9 @@
{
"files": [
{ "path": "./dist/clerk.js", "maxSize": "590kB" },
- { "path": "./dist/clerk.browser.js", "maxSize": "72.5KB" },
+ { "path": "./dist/clerk.browser.js", "maxSize": "72.65KB" },
{ "path": "./dist/clerk.headless*.js", "maxSize": "55KB" },
- { "path": "./dist/ui-common*.js", "maxSize": "98KB" },
+ { "path": "./dist/ui-common*.js", "maxSize": "98.1KB" },
{ "path": "./dist/vendors*.js", "maxSize": "36KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "35.5KB" },
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
@@ -21,7 +21,7 @@
{ "path": "./dist/keylessPrompt*.js", "maxSize": "5.9KB" },
{ "path": "./dist/pricingTable*.js", "maxSize": "5KB" },
{ "path": "./dist/checkout*.js", "maxSize": "3KB" },
- { "path": "./dist/paymentSources*.js", "maxSize": "8KB" },
+ { "path": "./dist/paymentSources*.js", "maxSize": "8.1KB" },
{ "path": "./dist/up-billing-page*.js", "maxSize": "1KB" },
{ "path": "./dist/op-billing-page*.js", "maxSize": "1KB" },
{ "path": "./dist/sessionTasks*.js", "maxSize": "1KB" }
diff --git a/packages/clerk-js/src/core/modules/commerce/Commerce.ts b/packages/clerk-js/src/core/modules/commerce/Commerce.ts
index 02d22c14984..6ac7674ca63 100644
--- a/packages/clerk-js/src/core/modules/commerce/Commerce.ts
+++ b/packages/clerk-js/src/core/modules/commerce/Commerce.ts
@@ -9,6 +9,7 @@ import type {
ClerkPaginatedResponse,
} from '@clerk/types';
+import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOffsetSearchParams';
import {
__experimental_CommerceInitializedPaymentSource,
__experimental_CommercePaymentSource,
@@ -27,35 +28,42 @@ export class __experimental_Commerce implements __experimental_CommerceNamespace
}
initializePaymentSource = async (params: __experimental_InitializePaymentSourceParams) => {
+ const { orgId, ...rest } = params;
const json = (
await BaseResource._fetch({
- path: `/me/commerce/payment_sources/initialize`,
+ path: orgId
+ ? `/organizations/${orgId}/commerce/payment_sources/initialize`
+ : `/me/commerce/payment_sources/initialize`,
method: 'POST',
- body: params as any,
+ body: rest as any,
})
)?.response as unknown as __experimental_CommerceInitializedPaymentSourceJSON;
return new __experimental_CommerceInitializedPaymentSource(json);
};
addPaymentSource = async (params: __experimental_AddPaymentSourceParams) => {
+ const { orgId, ...rest } = params;
+
const json = (
await BaseResource._fetch({
- path: `/me/commerce/payment_sources`,
+ path: orgId ? `/organizations/${orgId}/commerce/payment_sources` : `/me/commerce/payment_sources`,
method: 'POST',
- body: params as any,
+ body: rest as any,
})
)?.response as unknown as __experimental_CommercePaymentSourceJSON;
return new __experimental_CommercePaymentSource(json);
};
getPaymentSources = async (params: __experimental_GetPaymentSourcesParams) => {
+ const { orgId, ...rest } = params;
+
return await BaseResource._fetch({
- path: `/me/commerce/payment_sources`,
+ path: orgId ? `/organizations/${orgId}/commerce/payment_sources` : `/me/commerce/payment_sources`,
method: 'GET',
- search: { orgId: params.orgId || '' },
+ search: convertPageToOffsetSearchParams(rest),
}).then(res => {
const { data: paymentSources, total_count } =
- res as unknown as ClerkPaginatedResponse<__experimental_CommercePaymentSourceJSON>;
+ res?.response as unknown as ClerkPaginatedResponse<__experimental_CommercePaymentSourceJSON>;
return {
total_count,
data: paymentSources.map(paymentSource => new __experimental_CommercePaymentSource(paymentSource)),
diff --git a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
index f6b2ec40717..aacab3ecea0 100644
--- a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
+++ b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
@@ -7,9 +7,11 @@ import type {
__experimental_CommerceSubscriptionResource,
__experimental_CreateCheckoutParams,
__experimental_GetPlansParams,
+ __experimental_GetSubscriptionsParams,
ClerkPaginatedResponse,
} from '@clerk/types';
+import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOffsetSearchParams';
import {
__experimental_CommerceCheckout,
__experimental_CommercePlan,
@@ -29,10 +31,15 @@ export class __experimental_CommerceBilling implements __experimental_CommerceBi
return defaultProduct?.plans.map(plan => new __experimental_CommercePlan(plan)) || [];
};
- getSubscriptions = async (): Promise> => {
+ getSubscriptions = async (
+ params: __experimental_GetSubscriptionsParams,
+ ): Promise> => {
+ const { orgId, ...rest } = params;
+
return await BaseResource._fetch({
- path: `/me/subscriptions`,
+ path: orgId ? `/organizations/${orgId}/subscriptions` : `/me/commerce/subscriptions`,
method: 'GET',
+ search: convertPageToOffsetSearchParams(rest),
}).then(res => {
const { data: subscriptions, total_count } =
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommerceSubscriptionJSON>;
@@ -45,11 +52,12 @@ export class __experimental_CommerceBilling implements __experimental_CommerceBi
};
startCheckout = async (params: __experimental_CreateCheckoutParams) => {
+ const { orgId, ...rest } = params;
const json = (
await BaseResource._fetch<__experimental_CommerceCheckoutJSON>({
- path: `/me/commerce/checkouts`,
+ path: orgId ? `/organizations/${orgId}/commerce/checkouts` : `/me/commerce/checkouts`,
method: 'POST',
- body: params as any,
+ body: rest as any,
})
)?.response as unknown as __experimental_CommerceCheckoutJSON;
diff --git a/packages/clerk-js/src/core/resources/CommerceCheckout.ts b/packages/clerk-js/src/core/resources/CommerceCheckout.ts
index b1f5be96a37..e488905a6c2 100644
--- a/packages/clerk-js/src/core/resources/CommerceCheckout.ts
+++ b/packages/clerk-js/src/core/resources/CommerceCheckout.ts
@@ -16,8 +16,6 @@ import {
} from './internal';
export class __experimental_CommerceCheckout extends BaseResource implements __experimental_CommerceCheckoutResource {
- pathRoot = '/me/commerce/checkouts';
-
id!: string;
externalClientSecret!: string;
externalGatewayId!: string;
@@ -55,10 +53,13 @@ export class __experimental_CommerceCheckout extends BaseResource implements __e
return this;
}
- confirm = (params?: __experimental_ConfirmCheckoutParams): Promise => {
+ confirm = (params: __experimental_ConfirmCheckoutParams): Promise => {
+ const { orgId, ...rest } = params;
return this._basePatch({
- path: this.path('confirm'),
- body: params as any,
+ path: orgId
+ ? `/organizations/${orgId}/commerce/checkouts/${this.id}/confirm`
+ : `/me/commerce/checkouts/${this.id}/confirm`,
+ body: rest as any,
});
};
}
diff --git a/packages/clerk-js/src/core/resources/CommercePaymentSource.ts b/packages/clerk-js/src/core/resources/CommercePaymentSource.ts
index bcd0aca4bd0..5dbded2b4b1 100644
--- a/packages/clerk-js/src/core/resources/CommercePaymentSource.ts
+++ b/packages/clerk-js/src/core/resources/CommercePaymentSource.ts
@@ -4,6 +4,8 @@ import type {
__experimental_CommercePaymentSourceJSON,
__experimental_CommercePaymentSourceResource,
__experimental_CommercePaymentSourceStatus,
+ __experimental_MakeDefaultPaymentSourceParams,
+ __experimental_RemovePaymentSourceParams,
DeletedObjectJSON,
} from '@clerk/types';
@@ -19,6 +21,7 @@ export class __experimental_CommercePaymentSource
cardType!: string;
isDefault!: boolean;
status!: __experimental_CommercePaymentSourceStatus;
+ walletType: string | undefined;
constructor(data: __experimental_CommercePaymentSourceJSON) {
super();
@@ -34,21 +37,39 @@ export class __experimental_CommercePaymentSource
this.last4 = data.last4;
this.paymentMethod = data.payment_method;
this.cardType = data.card_type;
- this.isDefault = false;
+ this.isDefault = data.is_default;
this.status = data.status;
+ this.walletType = data.wallet_type ?? undefined;
+
return this;
}
- public async remove() {
+ public async remove(params?: __experimental_RemovePaymentSourceParams) {
+ const { orgId } = params ?? {};
const json = (
await BaseResource._fetch({
- path: `/me/commerce/payment_sources/${this.id}`,
+ path: orgId
+ ? `/organizations/${orgId}/commerce/payment_sources/${this.id}`
+ : `/me/commerce/payment_sources/${this.id}`,
method: 'DELETE',
})
)?.response as unknown as DeletedObjectJSON;
return new DeletedObject(json);
}
+
+ public async makeDefault(params?: __experimental_MakeDefaultPaymentSourceParams) {
+ const { orgId } = params ?? {};
+ await BaseResource._fetch({
+ path: orgId
+ ? `/organizations/${orgId}/commerce/payers/default_payment_source`
+ : `/me/commerce/payers/default_payment_source`,
+ method: 'PUT',
+ body: { payment_source_id: this.id } as any,
+ });
+
+ return null;
+ }
}
export class __experimental_CommerceInitializedPaymentSource
diff --git a/packages/clerk-js/src/core/resources/CommerceSubscription.ts b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
index e25f95c8612..cb280bead0b 100644
--- a/packages/clerk-js/src/core/resources/CommerceSubscription.ts
+++ b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
@@ -1,4 +1,5 @@
import type {
+ __experimental_CancelSubscriptionParams,
__experimental_CommerceSubscriptionJSON,
__experimental_CommerceSubscriptionPlanPeriod,
__experimental_CommerceSubscriptionResource,
@@ -37,10 +38,13 @@ export class __experimental_CommerceSubscription
return this;
}
- public async cancel() {
+ public async cancel(params: __experimental_CancelSubscriptionParams) {
+ const { orgId } = params;
const json = (
await BaseResource._fetch({
- path: `/me/commerce/subscriptions/${this.id}`,
+ path: orgId
+ ? `/organizations/${orgId}/commerce/subscriptions/${this.id}`
+ : `/me/commerce/subscriptions/${this.id}`,
method: 'DELETE',
})
)?.response as unknown as DeletedObjectJSON;
diff --git a/packages/clerk-js/src/core/resources/Organization.ts b/packages/clerk-js/src/core/resources/Organization.ts
index 9ca83f101a0..e452cb35742 100644
--- a/packages/clerk-js/src/core/resources/Organization.ts
+++ b/packages/clerk-js/src/core/resources/Organization.ts
@@ -240,16 +240,11 @@ export class Organization extends BaseResource implements OrganizationResource {
__experimental_getSubscriptions = async (
getSubscriptionsParams?: __experimental_GetSubscriptionsParams,
): Promise> => {
- return await BaseResource._fetch(
- {
- path: `/organizations/${this.id}/subscriptions`,
- method: 'GET',
- search: convertPageToOffsetSearchParams(getSubscriptionsParams),
- },
- {
- forceUpdateClient: true,
- },
- ).then(res => {
+ return await BaseResource._fetch({
+ path: `/organizations/${this.id}/subscriptions`,
+ method: 'GET',
+ search: convertPageToOffsetSearchParams(getSubscriptionsParams),
+ }).then(res => {
const { data: subscriptions, total_count } =
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommerceSubscriptionJSON>;
diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx
index ad400d13aa9..31d19687709 100644
--- a/packages/clerk-js/src/ui/Components.tsx
+++ b/packages/clerk-js/src/ui/Components.tsx
@@ -541,7 +541,7 @@ const Components = (props: ComponentsProps) => {
diff --git a/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx b/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx
index dd869dbc7d0..c3043848e7e 100644
--- a/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx
+++ b/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx
@@ -12,6 +12,7 @@ export const __experimental_Checkout = (props: __experimental_CheckoutProps) =>
<__experimental_CheckoutContext.Provider
value={{
componentName: 'Checkout',
+ ...props,
}}
>
diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
index 2d98f773f06..0bd7b23a7f1 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 { useClerk } from '@clerk/shared/react';
+import { useClerk, useOrganization } from '@clerk/shared/react';
import type {
__experimental_CommerceCheckoutResource,
__experimental_CommerceMoney,
@@ -8,6 +8,7 @@ import type {
} from '@clerk/types';
import { useMemo, useState } from 'react';
+import { __experimental_PaymentSourcesContext, useCheckoutContext } from '../../contexts';
import { Box, Button, Col, descriptors, Flex, Form, Icon, localizationKeys, Text } from '../../customizables';
import { Alert, Disclosure, Divider, Drawer, LineItems, Select, SelectButton, SelectOptionList } from '../../elements';
import { useFetch } from '../../hooks';
@@ -90,17 +91,29 @@ const CheckoutFormElements = ({
onCheckoutComplete: (checkout: __experimental_CommerceCheckoutResource) => void;
}) => {
const { __experimental_commerce } = useClerk();
+ const { organization } = useOrganization();
+ const { subscriberType } = useCheckoutContext();
const [openAccountFundsDropDown, setOpenAccountFundsDropDown] = useState(true);
const [openAddNewSourceDropDown, setOpenAddNewSourceDropDown] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState();
- const { data } = useFetch(__experimental_commerce?.getPaymentSources, 'commerce-payment-sources');
+ const { data } = useFetch(
+ __experimental_commerce?.getPaymentSources,
+ {
+ ...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
+ },
+ undefined,
+ 'commerce-payment-sources',
+ );
const { data: paymentSources } = data || { data: [] };
const confirmCheckout = async ({ paymentSourceId }: { paymentSourceId: string }) => {
return checkout
- .confirm({ paymentSourceId })
+ .confirm({
+ paymentSourceId,
+ ...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
+ })
.then(newCheckout => {
onCheckoutComplete(newCheckout);
})
@@ -176,16 +189,18 @@ const CheckoutFormElements = ({
{/* TODO(@Commerce): needs localization */}
-
+ <__experimental_PaymentSourcesContext.Provider value={{ componentName: 'PaymentSources', subscriberType }}>
+
+
diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
index a24982f2e80..d8cda15a918 100644
--- a/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
+++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
@@ -6,12 +6,12 @@ import { CheckoutComplete } from './CheckoutComplete';
import { CheckoutForm } from './CheckoutForm';
export const CheckoutPage = (props: __experimental_CheckoutProps) => {
- const { planId, planPeriod, orgId, onSubscriptionComplete } = props;
+ const { planId, planPeriod, subscriberType, onSubscriptionComplete } = props;
const { checkout, updateCheckout, isLoading } = useCheckout({
planId,
planPeriod,
- orgId,
+ subscriberType,
});
const onCheckoutComplete = (newCheckout: __experimental_CommerceCheckoutResource) => {
diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationBillingPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationBillingPage.tsx
index 6998ab2c89e..5b47fcf489e 100644
--- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationBillingPage.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationBillingPage.tsx
@@ -1,4 +1,4 @@
-import { __experimental_PricingTableContext } from '../../contexts';
+import { __experimental_PaymentSourcesContext, __experimental_PricingTableContext } from '../../contexts';
import { Col, descriptors, localizationKeys } from '../../customizables';
import {
Card,
@@ -11,6 +11,7 @@ import {
useCardState,
withCardStateProvider,
} from '../../elements';
+import { __experimental_PaymentSources } from '../PaymentSources/PaymentSources';
import { __experimental_PricingTable } from '../PricingTable';
export const OrganizationBillingPage = withCardStateProvider(() => {
@@ -58,7 +59,13 @@ export const OrganizationBillingPage = withCardStateProvider(() => {
Invoices
- Payment Sources
+
+ <__experimental_PaymentSourcesContext.Provider
+ value={{ componentName: 'PaymentSources', subscriberType: 'org' }}
+ >
+ <__experimental_PaymentSources />
+
+
diff --git a/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx b/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
index 5d61a47bd52..a727d392159 100644
--- a/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
+++ b/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx
@@ -1,4 +1,4 @@
-import { useClerk } from '@clerk/shared/react';
+import { useClerk, useOrganization } from '@clerk/shared/react';
import type {
__experimental_CommerceCheckoutResource,
__experimental_CommercePaymentSourceResource,
@@ -10,7 +10,7 @@ import type { Appearance as StripeAppearance, Stripe } from '@stripe/stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import { useEffect, useRef, useState } from 'react';
-import { useEnvironment } from '../../contexts';
+import { useEnvironment, usePaymentSourcesContext } from '../../contexts';
import { descriptors, Flex, localizationKeys, Spinner, useAppearance } from '../../customizables';
import { Alert, Form, FormButtons, FormContainer, withCardStateProvider } from '../../elements';
import { useFetch } from '../../hooks/useFetch';
@@ -29,6 +29,8 @@ export const AddPaymentSource = (props: AddPaymentSourceProps) => {
const { checkout, submitLabel, onSuccess, cancelAction } = props;
const { __experimental_commerce } = useClerk();
const { __experimental_commerceSettings } = useEnvironment();
+ const { organization } = useOrganization();
+ const { subscriberType } = usePaymentSourcesContext();
const stripePromiseRef = useRef | null>(null);
const [stripe, setStripe] = useState(null);
@@ -61,7 +63,10 @@ export const AddPaymentSource = (props: AddPaymentSourceProps) => {
!checkout ? __experimental_commerce.initializePaymentSource : undefined,
{
gateway: 'stripe',
+ ...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
},
+ undefined,
+ 'commerce-payment-source-initialize',
);
const externalGatewayId = checkout?.externalGatewayId ?? initializedPaymentSource?.externalGatewayId;
@@ -124,6 +129,8 @@ const AddPaymentSourceForm = withCardStateProvider(
const stripe = useStripe();
const elements = useElements();
const { displayConfig } = useEnvironment();
+ const { organization } = useOrganization();
+ const { subscriberType } = usePaymentSourcesContext();
const [submitError, setSubmitError] = useState();
const onSubmit = async (e: React.FormEvent) => {
@@ -148,6 +155,7 @@ const AddPaymentSourceForm = withCardStateProvider(
const paymentSource = await __experimental_commerce.addPaymentSource({
gateway: 'stripe',
paymentToken: setupIntent.payment_method as string,
+ ...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
});
void onSuccess(paymentSource);
diff --git a/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx b/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx
index 5e6d0676ebb..64355c1915c 100644
--- a/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx
+++ b/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx
@@ -1,14 +1,15 @@
-import { useClerk } from '@clerk/shared/react';
+import { useClerk, useOrganization } from '@clerk/shared/react';
import type { __experimental_CommercePaymentSourceResource, __experimental_PaymentSourcesProps } from '@clerk/types';
import { Fragment, useRef } from 'react';
import { RemoveResourceForm } from '../../common';
+import { usePaymentSourcesContext } from '../../contexts';
import { Badge, Flex, Icon, localizationKeys, Text } from '../../customizables';
import { ProfileSection, ThreeDotsMenu, useCardState } from '../../elements';
import { Action } from '../../elements/Action';
import { useActionContext } from '../../elements/Action/ActionRoot';
import { useFetch } from '../../hooks';
-import { CreditCard } from '../../icons';
+import { ApplePay, CreditCard } from '../../icons';
import { handleError } from '../../utils';
import { AddPaymentSource } from './AddPaymentSource';
@@ -38,6 +39,8 @@ const RemoveScreen = ({
}) => {
const { close } = useActionContext();
const card = useCardState();
+ const { subscriberType } = usePaymentSourcesContext();
+ const { organization } = useOrganization();
const ref = useRef(
`${paymentSource.paymentMethod === 'card' ? paymentSource.cardType : paymentSource.paymentMethod} ${paymentSource.paymentMethod === 'card' ? `⋯ ${paymentSource.last4}` : '-'}`,
);
@@ -48,7 +51,7 @@ const RemoveScreen = ({
const removePaymentSource = async () => {
await paymentSource
- .remove()
+ .remove({ orgId: subscriberType === 'org' ? organization?.id : undefined })
.then(revalidate)
.catch((error: Error) => {
handleError(error, [], card.setError);
@@ -80,11 +83,17 @@ const RemoveScreen = ({
);
};
-export const __experimental_PaymentSources = (props: __experimental_PaymentSourcesProps) => {
- const { orgId } = props;
+const PaymentSources = (_: __experimental_PaymentSourcesProps) => {
const { __experimental_commerce } = useClerk();
+ const { organization } = useOrganization();
+ const { subscriberType } = usePaymentSourcesContext();
- const { data, revalidate } = useFetch(__experimental_commerce?.getPaymentSources, { orgId });
+ const { data, revalidate } = useFetch(
+ __experimental_commerce?.getPaymentSources,
+ { ...(subscriberType === 'org' ? { orgId: organization?.id } : {}) },
+ undefined,
+ 'commerce-user-payment-sources',
+ );
const { data: paymentSources } = data || { data: [] };
return (
@@ -104,12 +113,10 @@ export const __experimental_PaymentSources = (props: __experimental_PaymentSourc
gap={2}
align='baseline'
>
- {paymentSource.paymentMethod === 'card' && (
-
- )}
+
({ color: t.colors.$colorText, textTransform: 'capitalize' })}
truncate
@@ -131,7 +138,10 @@ export const __experimental_PaymentSources = (props: __experimental_PaymentSourc
/>
)}
-
+
@@ -161,8 +171,19 @@ export const __experimental_PaymentSources = (props: __experimental_PaymentSourc
);
};
-const PaymentSourceMenu = ({ paymentSource }: { paymentSource: __experimental_CommercePaymentSourceResource }) => {
+export const __experimental_PaymentSources = PaymentSources;
+
+const PaymentSourceMenu = ({
+ paymentSource,
+ revalidate,
+}: {
+ paymentSource: __experimental_CommercePaymentSourceResource;
+ revalidate: () => void;
+}) => {
const { open } = useActionContext();
+ const card = useCardState();
+ const { organization } = useOrganization();
+ const { subscriberType } = usePaymentSourcesContext();
const actions = [
{
@@ -172,5 +193,20 @@ const PaymentSourceMenu = ({ paymentSource }: { paymentSource: __experimental_Co
},
];
+ if (!paymentSource.isDefault) {
+ actions.unshift({
+ label: localizationKeys('userProfile.__experimental_billingPage.paymentSourcesSection.actionLabel__default'),
+ isDestructive: false,
+ onClick: () => {
+ paymentSource
+ .makeDefault({ orgId: subscriberType === 'org' ? organization?.id : undefined })
+ .then(revalidate)
+ .catch((error: Error) => {
+ handleError(error, [], card.setError);
+ });
+ },
+ });
+ }
+
return ;
};
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx
index 58d37abf5cc..5c4ab77bfdb 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx
@@ -1,4 +1,4 @@
-import { useClerk, useOrganization } from '@clerk/shared/react';
+import { useClerk } from '@clerk/shared/react';
import type {
__experimental_CommercePlanResource,
__experimental_CommerceSubscriptionPlanPeriod,
@@ -15,10 +15,9 @@ import { PricingTableDefault } from './PricingTableDefault';
import { PricingTableMatrix } from './PricingTableMatrix';
import { SubscriptionDetailDrawer } from './SubscriptionDetailDrawer';
-export const __experimental_PricingTable = (props: __experimental_PricingTableProps) => {
+const PricingTable = (props: __experimental_PricingTableProps) => {
const clerk = useClerk();
- const { organization } = useOrganization();
- const { mode = 'mounted', subscriberType = 'user' } = usePricingTableContext();
+ const { mode = 'mounted', subscriberType } = usePricingTableContext();
const isCompact = mode === 'modal';
const { plans, subscriptions, revalidate } = usePlans({ subscriberType });
@@ -40,7 +39,7 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
clerk.__internal_openCheckout({
planId: plan.id,
planPeriod,
- orgId: subscriberType === 'org' ? organization?.id : undefined,
+ subscriberType,
onSubscriptionComplete: onSubscriptionChange,
portalId: mode === 'modal' ? PROFILE_CARD_SCROLLBOX_ID : undefined,
});
@@ -80,6 +79,7 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
isOpen={showSubscriptionDetailDrawer}
setIsOpen={setShowSubscriptionDetailDrawer}
subscription={detailSubscription}
+ subscriberType={subscriberType}
setPlanPeriod={setPlanPeriod}
strategy={mode === 'mounted' ? 'fixed' : 'absolute'}
portalProps={{
@@ -91,3 +91,5 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
>
);
};
+
+export const __experimental_PricingTable = PricingTable;
diff --git a/packages/clerk-js/src/ui/components/PricingTable/SubscriptionDetailDrawer.tsx b/packages/clerk-js/src/ui/components/PricingTable/SubscriptionDetailDrawer.tsx
index 6e845cc3f44..90cb9ad5992 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/SubscriptionDetailDrawer.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/SubscriptionDetailDrawer.tsx
@@ -1,5 +1,7 @@
+import { useOrganization } from '@clerk/shared/react';
import type {
__experimental_CommercePlanResource,
+ __experimental_CommerceSubscriberType,
__experimental_CommerceSubscriptionPlanPeriod,
__experimental_CommerceSubscriptionResource,
ClerkAPIError,
@@ -33,6 +35,7 @@ type SubscriptionDetailDrawerProps = {
portalProps?: DrawerRootProps['portalProps'];
strategy: DrawerRootProps['strategy'];
subscription?: __experimental_CommerceSubscriptionResource;
+ subscriberType: __experimental_CommerceSubscriberType;
setPlanPeriod: (p: __experimental_CommerceSubscriptionPlanPeriod) => void;
onSubscriptionCancel: () => void;
};
@@ -43,9 +46,11 @@ export function SubscriptionDetailDrawer({
portalProps,
strategy,
subscription,
+ subscriberType,
setPlanPeriod,
onSubscriptionCancel,
}: SubscriptionDetailDrawerProps) {
+ const { organization } = useOrganization();
const [showConfirmation, setShowConfirmation] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [cancelError, setCancelError] = useState();
@@ -59,7 +64,7 @@ export function SubscriptionDetailDrawer({
setIsSubmitting(true);
await subscription
- .cancel()
+ .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
.then(() => {
setIsSubmitting(false);
onSubscriptionCancel();
diff --git a/packages/clerk-js/src/ui/components/UserProfile/BillingPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/BillingPage.tsx
index 824dbce854a..222c72e1f05 100644
--- a/packages/clerk-js/src/ui/components/UserProfile/BillingPage.tsx
+++ b/packages/clerk-js/src/ui/components/UserProfile/BillingPage.tsx
@@ -1,4 +1,4 @@
-import { __experimental_PricingTableContext } from '../../contexts';
+import { __experimental_PaymentSourcesContext, __experimental_PricingTableContext } from '../../contexts';
import { Col, descriptors, localizationKeys } from '../../customizables';
import {
Card,
@@ -58,7 +58,9 @@ export const BillingPage = withCardStateProvider(() => {
Invoices
- <__experimental_PaymentSources />
+ <__experimental_PaymentSourcesContext.Provider value={{ componentName: 'PaymentSources' }}>
+ <__experimental_PaymentSources />
+
diff --git a/packages/clerk-js/src/ui/contexts/components/PaymentSources.ts b/packages/clerk-js/src/ui/contexts/components/PaymentSources.ts
new file mode 100644
index 00000000000..6f011532189
--- /dev/null
+++ b/packages/clerk-js/src/ui/contexts/components/PaymentSources.ts
@@ -0,0 +1,21 @@
+import { createContext, useContext } from 'react';
+
+import type { __experimental_PaymentSourcesCtx } from '../../types';
+
+export const __experimental_PaymentSourcesContext = createContext<__experimental_PaymentSourcesCtx | null>(null);
+
+export const usePaymentSourcesContext = () => {
+ const context = useContext(__experimental_PaymentSourcesContext);
+
+ if (!context || context.componentName !== 'PaymentSources') {
+ throw new Error('Clerk: usePaymentSourcesContext called outside PaymentSources.');
+ }
+
+ const { componentName, ...ctx } = context;
+
+ return {
+ ...ctx,
+ componentName,
+ subscriberType: ctx.subscriberType || 'user',
+ };
+};
diff --git a/packages/clerk-js/src/ui/contexts/components/PricingTable.ts b/packages/clerk-js/src/ui/contexts/components/PricingTable.ts
index 862a0921c2d..f989d523e4d 100644
--- a/packages/clerk-js/src/ui/contexts/components/PricingTable.ts
+++ b/packages/clerk-js/src/ui/contexts/components/PricingTable.ts
@@ -16,5 +16,6 @@ export const usePricingTableContext = () => {
return {
...ctx,
componentName,
+ subscriberType: ctx.subscriberType || 'user',
};
};
diff --git a/packages/clerk-js/src/ui/contexts/components/index.ts b/packages/clerk-js/src/ui/contexts/components/index.ts
index 099c80876e2..70d0afa6da4 100644
--- a/packages/clerk-js/src/ui/contexts/components/index.ts
+++ b/packages/clerk-js/src/ui/contexts/components/index.ts
@@ -12,3 +12,4 @@ export * from './GoogleOneTap';
export * from './Waitlist';
export * from './PricingTable';
export * from './Checkout';
+export * from './PaymentSources';
diff --git a/packages/clerk-js/src/ui/hooks/useCheckout.ts b/packages/clerk-js/src/ui/hooks/useCheckout.ts
index 5f307d6f869..80b09fb79ba 100644
--- a/packages/clerk-js/src/ui/hooks/useCheckout.ts
+++ b/packages/clerk-js/src/ui/hooks/useCheckout.ts
@@ -1,18 +1,19 @@
-import { useClerk } from '@clerk/shared/react';
+import { useClerk, useOrganization } from '@clerk/shared/react';
import type { __experimental_CheckoutProps, __experimental_CommerceCheckoutResource } from '@clerk/types';
import { useCallback, useEffect, useState } from 'react';
import { useFetch } from './useFetch';
export const useCheckout = (props: __experimental_CheckoutProps) => {
- const { planId, planPeriod, orgId } = props;
+ const { planId, planPeriod, subscriberType = 'user' } = props;
const { __experimental_commerce } = useClerk();
+ const { organization } = useOrganization();
const [currentCheckout, setCurrentCheckout] = useState<__experimental_CommerceCheckoutResource | null>(null);
const { data: initialCheckout, isLoading } = useFetch(__experimental_commerce?.__experimental_billing.startCheckout, {
planId,
planPeriod,
- orgId,
+ ...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
});
useEffect(() => {
diff --git a/packages/clerk-js/src/ui/hooks/usePlans.ts b/packages/clerk-js/src/ui/hooks/usePlans.ts
index 5adf8501824..1642e3d1228 100644
--- a/packages/clerk-js/src/ui/hooks/usePlans.ts
+++ b/packages/clerk-js/src/ui/hooks/usePlans.ts
@@ -9,52 +9,46 @@ type UsePlansProps = {
};
export const usePlans = (props: UsePlansProps) => {
- const { subscriberType } = props;
+ const { subscriberType = 'user' } = props;
const { __experimental_commerce } = useClerk();
+ const { organization } = useOrganization();
- const { data: userSubscriptions, revalidate: revalidateUserSubscriptions } = useFetch(
+ const { data: subscriptions, revalidate: revalidateSubscriptions } = useFetch(
__experimental_commerce?.__experimental_billing.getSubscriptions,
- 'commerce-user-subscriptions',
+ { orgId: subscriberType === 'org' ? organization?.id : undefined },
+ undefined,
+ 'commerce-subscriptions',
);
- const { subscriptions: orgSubscriptions } = useOrganization({ subscriptions: true });
const { data: allPlans, revalidate: revalidatePlans } = useFetch(
__experimental_commerce?.__experimental_billing.getPlans,
{ subscriberType },
);
- const activeSubscriptions = useMemo(() => {
- if ((subscriberType === 'user' && !userSubscriptions) || (subscriberType === 'org' && !orgSubscriptions)) {
- return undefined;
- }
- return [...(subscriberType === 'user' ? userSubscriptions?.data || [] : orgSubscriptions?.data || [])];
- }, [userSubscriptions, orgSubscriptions, subscriberType]);
-
const plans = useMemo(() => {
- if (!activeSubscriptions) {
+ if (!subscriptions) {
return [];
}
return (
allPlans?.map(plan => {
- const activeSubscription = activeSubscriptions.find(sub => {
- return sub.plan.id === plan.id;
+ const activeSubscription = subscriptions.data.find(sub => {
+ return sub.plan.id === plan.id && sub.status === 'active';
});
plan.subscriptionIdForCurrentSubscriber = activeSubscription?.id;
return plan;
}) || []
);
- }, [allPlans, activeSubscriptions]);
+ }, [allPlans, subscriptions]);
- const revalidate = async () => {
+ const revalidate = () => {
// Revalidate the plans and subscriptions
- await orgSubscriptions?.revalidate?.();
- revalidateUserSubscriptions();
+ revalidateSubscriptions();
revalidatePlans();
};
return {
plans,
- subscriptions: activeSubscriptions || [],
+ subscriptions: subscriptions?.data || [],
revalidate,
};
};
diff --git a/packages/clerk-js/src/ui/icons/apple-pay.svg b/packages/clerk-js/src/ui/icons/apple-pay.svg
new file mode 100644
index 00000000000..3191e822be8
--- /dev/null
+++ b/packages/clerk-js/src/ui/icons/apple-pay.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/clerk-js/src/ui/icons/index.ts b/packages/clerk-js/src/ui/icons/index.ts
index 4be5e4ecc05..0582091aba6 100644
--- a/packages/clerk-js/src/ui/icons/index.ts
+++ b/packages/clerk-js/src/ui/icons/index.ts
@@ -5,6 +5,7 @@
* The above no-check is safe, as webpack will not allow compilation if for example a file is not resolved.
*/
export { default as Add } from './add.svg';
+export { default as ApplePay } from './apple-pay.svg';
export { default as ArrowLeftIcon } from './arrow-left.svg';
export { default as ArrowRightButtonIcon } from './arrow-right-button.svg';
export { default as ArrowRightIcon } from './arrow-right.svg';
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index 3e65b2d8acd..6ef9aa22a54 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -111,6 +111,11 @@ export type __experimental_CheckoutCtx = __experimental_CheckoutProps & {
componentName: 'Checkout';
};
+export type __experimental_PaymentSourcesCtx = {
+ componentName: 'PaymentSources';
+ subscriberType?: __experimental_CommerceSubscriberType;
+};
+
export type SessionTasksCtx = {
nextTask: () => Promise;
redirectUrlComplete?: string;
@@ -130,5 +135,4 @@ export type AvailableComponentCtx =
| WaitlistCtx
| __experimental_PricingTableCtx
| __experimental_CheckoutCtx;
-
export type AvailableComponentName = AvailableComponentCtx['componentName'];
diff --git a/packages/shared/src/react/hooks/useOrganization.tsx b/packages/shared/src/react/hooks/useOrganization.tsx
index 94a0fc74685..7cafd3f6ba4 100644
--- a/packages/shared/src/react/hooks/useOrganization.tsx
+++ b/packages/shared/src/react/hooks/useOrganization.tsx
@@ -339,7 +339,6 @@ export function useOrganization(params?: T): Us
const subscriptionsSafeValues = useWithSafeValues(subscriptionsListParams, {
initialPage: 1,
pageSize: 10,
- status: undefined,
keepPreviousData: false,
infinite: false,
});
@@ -391,7 +390,7 @@ export function useOrganization(params?: T): Us
: {
initialPage: subscriptionsSafeValues.initialPage,
pageSize: subscriptionsSafeValues.pageSize,
- status: subscriptionsSafeValues.status,
+ orgId: organization?.id,
};
const domains = usePagesOrInfinite>(
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index ff2db91a5d8..3bff3424f25 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -14,7 +14,11 @@ import type {
WaitlistTheme,
} from './appearance';
import type { ClientResource } from './client';
-import type { __experimental_CommerceNamespace, __experimental_CommerceSubscriptionPlanPeriod } from './commerce';
+import type {
+ __experimental_CommerceNamespace,
+ __experimental_CommerceSubscriberType,
+ __experimental_CommerceSubscriptionPlanPeriod,
+} from './commerce';
import type { CustomMenuItem } from './customMenuItems';
import type { CustomPage } from './customPages';
import type { InstanceType } from './instance';
@@ -1524,13 +1528,13 @@ export type __experimental_CheckoutProps = {
appearance?: CheckoutTheme;
planId?: string;
planPeriod?: __experimental_CommerceSubscriptionPlanPeriod;
- orgId?: string;
+ subscriberType?: __experimental_CommerceSubscriberType;
onSubscriptionComplete?: () => void;
portalId?: string;
};
export type __experimental_PaymentSourcesProps = {
- orgId?: string;
+ subscriberType?: __experimental_CommerceSubscriberType;
};
export interface HandleEmailLinkVerificationParams {
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index 320b2cea108..9b3a190f5cf 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -2,6 +2,9 @@ import type { DeletedObjectResource } from './deletedObject';
import type { ClerkPaginatedResponse, ClerkPaginationParams } from './pagination';
import type { ClerkResource } from './resource';
+type WithOptionalOrgType = T & {
+ orgId?: string;
+};
export interface __experimental_CommerceNamespace {
__experimental_billing: __experimental_CommerceBillingNamespace;
getPaymentSources: (
@@ -18,7 +21,7 @@ export interface __experimental_CommerceNamespace {
export interface __experimental_CommerceBillingNamespace {
getPlans: () => Promise<__experimental_CommercePlanResource[]>;
getSubscriptions: (
- params?: __experimental_GetSubscriptionsParams,
+ params: __experimental_GetSubscriptionsParams,
) => Promise>;
startCheckout: (params: __experimental_CreateCheckoutParams) => Promise<__experimental_CommerceCheckoutResource>;
}
@@ -69,18 +72,19 @@ export interface __experimental_CommerceFeatureResource extends ClerkResource {
export type __experimental_CommercePaymentSourceStatus = 'active' | 'expired' | 'disconnected';
-export interface __experimental_InitializePaymentSourceParams {
+export type __experimental_GetPaymentSourcesParams = WithOptionalOrgType;
+
+export type __experimental_InitializePaymentSourceParams = WithOptionalOrgType<{
gateway: 'stripe' | 'paypal';
-}
+}>;
-export interface __experimental_AddPaymentSourceParams {
+export type __experimental_AddPaymentSourceParams = WithOptionalOrgType<{
gateway: 'stripe' | 'paypal';
paymentToken: string;
-}
+}>;
-export interface __experimental_GetPaymentSourcesParams {
- orgId?: string;
-}
+export type __experimental_RemovePaymentSourceParams = WithOptionalOrgType;
+export type __experimental_MakeDefaultPaymentSourceParams = WithOptionalOrgType;
export interface __experimental_CommercePaymentSourceResource extends ClerkResource {
id: string;
@@ -89,7 +93,9 @@ export interface __experimental_CommercePaymentSourceResource extends ClerkResou
cardType: string;
isDefault: boolean;
status: __experimental_CommercePaymentSourceStatus;
- remove: () => Promise;
+ walletType: string | undefined;
+ remove: (params?: __experimental_RemovePaymentSourceParams) => Promise;
+ makeDefault: (params?: __experimental_MakeDefaultPaymentSourceParams) => Promise;
}
export interface __experimental_CommerceInitializedPaymentSourceResource extends ClerkResource {
@@ -107,9 +113,8 @@ export interface __experimental_CommerceInvoiceResource extends ClerkResource {
status: string;
}
-export type __experimental_GetSubscriptionsParams = ClerkPaginationParams<{
- status?: __experimental_CommerceSubscriptionStatus;
-}>;
+export type __experimental_GetSubscriptionsParams = WithOptionalOrgType;
+export type __experimental_CancelSubscriptionParams = WithOptionalOrgType;
export interface __experimental_CommerceSubscriptionResource extends ClerkResource {
id: string;
@@ -117,7 +122,7 @@ export interface __experimental_CommerceSubscriptionResource extends ClerkResour
plan: __experimental_CommercePlanResource;
planPeriod: __experimental_CommerceSubscriptionPlanPeriod;
status: __experimental_CommerceSubscriptionStatus;
- cancel: () => Promise;
+ cancel: (params: __experimental_CancelSubscriptionParams) => Promise;
}
export interface __experimental_CommerceMoney {
@@ -134,15 +139,14 @@ export interface __experimental_CommerceTotals {
totalDueNow?: __experimental_CommerceMoney;
}
-export interface __experimental_CreateCheckoutParams {
+export type __experimental_CreateCheckoutParams = WithOptionalOrgType<{
planId: string;
planPeriod: __experimental_CommerceSubscriptionPlanPeriod;
- orgId?: string;
-}
+}>;
-export interface __experimental_ConfirmCheckoutParams {
+export type __experimental_ConfirmCheckoutParams = WithOptionalOrgType<{
paymentSourceId?: string;
-}
+}>;
export interface __experimental_CommerceCheckoutResource extends ClerkResource {
id: string;
@@ -155,5 +159,5 @@ export interface __experimental_CommerceCheckoutResource extends ClerkResource {
status: string;
totals: __experimental_CommerceTotals;
subscription?: __experimental_CommerceSubscriptionResource;
- confirm: (params?: __experimental_ConfirmCheckoutParams) => Promise<__experimental_CommerceCheckoutResource>;
+ confirm: (params: __experimental_ConfirmCheckoutParams) => Promise<__experimental_CommerceCheckoutResource>;
}
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index 2e49afde77c..daee8d69ebd 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -627,7 +627,9 @@ export interface __experimental_CommercePaymentSourceJSON extends ClerkResourceJ
last4: string;
payment_method: string;
card_type: string;
+ is_default: boolean;
status: __experimental_CommercePaymentSourceStatus;
+ wallet_type: string | null;
}
export interface __experimental_CommerceInitializedPaymentSourceJSON extends ClerkResourceJSON {