Skip to content

Commit cfa94b8

Browse files
authored
feat(clerk-js): Add payment sources to OrgProfile, org-specific commerce endpoints (#5554)
1 parent c8e69db commit cfa94b8

30 files changed

+278
-123
lines changed

.changeset/great-moons-occur.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/shared': patch
4+
'@clerk/types': patch
5+
---
6+
7+
Add Payment Sources to `<OrgProfile />`, hook up all org-related payment source and checkout methods to the org-specific endpoints

packages/clerk-js/bundlewatch.config.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"files": [
33
{ "path": "./dist/clerk.js", "maxSize": "590kB" },
4-
{ "path": "./dist/clerk.browser.js", "maxSize": "72.5KB" },
4+
{ "path": "./dist/clerk.browser.js", "maxSize": "72.65KB" },
55
{ "path": "./dist/clerk.headless*.js", "maxSize": "55KB" },
6-
{ "path": "./dist/ui-common*.js", "maxSize": "98KB" },
6+
{ "path": "./dist/ui-common*.js", "maxSize": "98.1KB" },
77
{ "path": "./dist/vendors*.js", "maxSize": "36KB" },
88
{ "path": "./dist/coinbase*.js", "maxSize": "35.5KB" },
99
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
@@ -21,7 +21,7 @@
2121
{ "path": "./dist/keylessPrompt*.js", "maxSize": "5.9KB" },
2222
{ "path": "./dist/pricingTable*.js", "maxSize": "5KB" },
2323
{ "path": "./dist/checkout*.js", "maxSize": "3KB" },
24-
{ "path": "./dist/paymentSources*.js", "maxSize": "8KB" },
24+
{ "path": "./dist/paymentSources*.js", "maxSize": "8.1KB" },
2525
{ "path": "./dist/up-billing-page*.js", "maxSize": "1KB" },
2626
{ "path": "./dist/op-billing-page*.js", "maxSize": "1KB" },
2727
{ "path": "./dist/sessionTasks*.js", "maxSize": "1KB" }

packages/clerk-js/src/core/modules/commerce/Commerce.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
ClerkPaginatedResponse,
1010
} from '@clerk/types';
1111

12+
import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOffsetSearchParams';
1213
import {
1314
__experimental_CommerceInitializedPaymentSource,
1415
__experimental_CommercePaymentSource,
@@ -27,35 +28,42 @@ export class __experimental_Commerce implements __experimental_CommerceNamespace
2728
}
2829

2930
initializePaymentSource = async (params: __experimental_InitializePaymentSourceParams) => {
31+
const { orgId, ...rest } = params;
3032
const json = (
3133
await BaseResource._fetch({
32-
path: `/me/commerce/payment_sources/initialize`,
34+
path: orgId
35+
? `/organizations/${orgId}/commerce/payment_sources/initialize`
36+
: `/me/commerce/payment_sources/initialize`,
3337
method: 'POST',
34-
body: params as any,
38+
body: rest as any,
3539
})
3640
)?.response as unknown as __experimental_CommerceInitializedPaymentSourceJSON;
3741
return new __experimental_CommerceInitializedPaymentSource(json);
3842
};
3943

4044
addPaymentSource = async (params: __experimental_AddPaymentSourceParams) => {
45+
const { orgId, ...rest } = params;
46+
4147
const json = (
4248
await BaseResource._fetch({
43-
path: `/me/commerce/payment_sources`,
49+
path: orgId ? `/organizations/${orgId}/commerce/payment_sources` : `/me/commerce/payment_sources`,
4450
method: 'POST',
45-
body: params as any,
51+
body: rest as any,
4652
})
4753
)?.response as unknown as __experimental_CommercePaymentSourceJSON;
4854
return new __experimental_CommercePaymentSource(json);
4955
};
5056

5157
getPaymentSources = async (params: __experimental_GetPaymentSourcesParams) => {
58+
const { orgId, ...rest } = params;
59+
5260
return await BaseResource._fetch({
53-
path: `/me/commerce/payment_sources`,
61+
path: orgId ? `/organizations/${orgId}/commerce/payment_sources` : `/me/commerce/payment_sources`,
5462
method: 'GET',
55-
search: { orgId: params.orgId || '' },
63+
search: convertPageToOffsetSearchParams(rest),
5664
}).then(res => {
5765
const { data: paymentSources, total_count } =
58-
res as unknown as ClerkPaginatedResponse<__experimental_CommercePaymentSourceJSON>;
66+
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommercePaymentSourceJSON>;
5967
return {
6068
total_count,
6169
data: paymentSources.map(paymentSource => new __experimental_CommercePaymentSource(paymentSource)),

packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import type {
77
__experimental_CommerceSubscriptionResource,
88
__experimental_CreateCheckoutParams,
99
__experimental_GetPlansParams,
10+
__experimental_GetSubscriptionsParams,
1011
ClerkPaginatedResponse,
1112
} from '@clerk/types';
1213

14+
import { convertPageToOffsetSearchParams } from '../../../utils/convertPageToOffsetSearchParams';
1315
import {
1416
__experimental_CommerceCheckout,
1517
__experimental_CommercePlan,
@@ -29,10 +31,15 @@ export class __experimental_CommerceBilling implements __experimental_CommerceBi
2931
return defaultProduct?.plans.map(plan => new __experimental_CommercePlan(plan)) || [];
3032
};
3133

32-
getSubscriptions = async (): Promise<ClerkPaginatedResponse<__experimental_CommerceSubscriptionResource>> => {
34+
getSubscriptions = async (
35+
params: __experimental_GetSubscriptionsParams,
36+
): Promise<ClerkPaginatedResponse<__experimental_CommerceSubscriptionResource>> => {
37+
const { orgId, ...rest } = params;
38+
3339
return await BaseResource._fetch({
34-
path: `/me/subscriptions`,
40+
path: orgId ? `/organizations/${orgId}/subscriptions` : `/me/commerce/subscriptions`,
3541
method: 'GET',
42+
search: convertPageToOffsetSearchParams(rest),
3643
}).then(res => {
3744
const { data: subscriptions, total_count } =
3845
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommerceSubscriptionJSON>;
@@ -45,11 +52,12 @@ export class __experimental_CommerceBilling implements __experimental_CommerceBi
4552
};
4653

4754
startCheckout = async (params: __experimental_CreateCheckoutParams) => {
55+
const { orgId, ...rest } = params;
4856
const json = (
4957
await BaseResource._fetch<__experimental_CommerceCheckoutJSON>({
50-
path: `/me/commerce/checkouts`,
58+
path: orgId ? `/organizations/${orgId}/commerce/checkouts` : `/me/commerce/checkouts`,
5159
method: 'POST',
52-
body: params as any,
60+
body: rest as any,
5361
})
5462
)?.response as unknown as __experimental_CommerceCheckoutJSON;
5563

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import {
1616
} from './internal';
1717

1818
export class __experimental_CommerceCheckout extends BaseResource implements __experimental_CommerceCheckoutResource {
19-
pathRoot = '/me/commerce/checkouts';
20-
2119
id!: string;
2220
externalClientSecret!: string;
2321
externalGatewayId!: string;
@@ -55,10 +53,13 @@ export class __experimental_CommerceCheckout extends BaseResource implements __e
5553
return this;
5654
}
5755

58-
confirm = (params?: __experimental_ConfirmCheckoutParams): Promise<this> => {
56+
confirm = (params: __experimental_ConfirmCheckoutParams): Promise<this> => {
57+
const { orgId, ...rest } = params;
5958
return this._basePatch({
60-
path: this.path('confirm'),
61-
body: params as any,
59+
path: orgId
60+
? `/organizations/${orgId}/commerce/checkouts/${this.id}/confirm`
61+
: `/me/commerce/checkouts/${this.id}/confirm`,
62+
body: rest as any,
6263
});
6364
};
6465
}

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

+24-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type {
44
__experimental_CommercePaymentSourceJSON,
55
__experimental_CommercePaymentSourceResource,
66
__experimental_CommercePaymentSourceStatus,
7+
__experimental_MakeDefaultPaymentSourceParams,
8+
__experimental_RemovePaymentSourceParams,
79
DeletedObjectJSON,
810
} from '@clerk/types';
911

@@ -19,6 +21,7 @@ export class __experimental_CommercePaymentSource
1921
cardType!: string;
2022
isDefault!: boolean;
2123
status!: __experimental_CommercePaymentSourceStatus;
24+
walletType: string | undefined;
2225

2326
constructor(data: __experimental_CommercePaymentSourceJSON) {
2427
super();
@@ -34,21 +37,39 @@ export class __experimental_CommercePaymentSource
3437
this.last4 = data.last4;
3538
this.paymentMethod = data.payment_method;
3639
this.cardType = data.card_type;
37-
this.isDefault = false;
40+
this.isDefault = data.is_default;
3841
this.status = data.status;
42+
this.walletType = data.wallet_type ?? undefined;
43+
3944
return this;
4045
}
4146

42-
public async remove() {
47+
public async remove(params?: __experimental_RemovePaymentSourceParams) {
48+
const { orgId } = params ?? {};
4349
const json = (
4450
await BaseResource._fetch({
45-
path: `/me/commerce/payment_sources/${this.id}`,
51+
path: orgId
52+
? `/organizations/${orgId}/commerce/payment_sources/${this.id}`
53+
: `/me/commerce/payment_sources/${this.id}`,
4654
method: 'DELETE',
4755
})
4856
)?.response as unknown as DeletedObjectJSON;
4957

5058
return new DeletedObject(json);
5159
}
60+
61+
public async makeDefault(params?: __experimental_MakeDefaultPaymentSourceParams) {
62+
const { orgId } = params ?? {};
63+
await BaseResource._fetch({
64+
path: orgId
65+
? `/organizations/${orgId}/commerce/payers/default_payment_source`
66+
: `/me/commerce/payers/default_payment_source`,
67+
method: 'PUT',
68+
body: { payment_source_id: this.id } as any,
69+
});
70+
71+
return null;
72+
}
5273
}
5374

5475
export class __experimental_CommerceInitializedPaymentSource

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
__experimental_CancelSubscriptionParams,
23
__experimental_CommerceSubscriptionJSON,
34
__experimental_CommerceSubscriptionPlanPeriod,
45
__experimental_CommerceSubscriptionResource,
@@ -37,10 +38,13 @@ export class __experimental_CommerceSubscription
3738
return this;
3839
}
3940

40-
public async cancel() {
41+
public async cancel(params: __experimental_CancelSubscriptionParams) {
42+
const { orgId } = params;
4143
const json = (
4244
await BaseResource._fetch({
43-
path: `/me/commerce/subscriptions/${this.id}`,
45+
path: orgId
46+
? `/organizations/${orgId}/commerce/subscriptions/${this.id}`
47+
: `/me/commerce/subscriptions/${this.id}`,
4448
method: 'DELETE',
4549
})
4650
)?.response as unknown as DeletedObjectJSON;

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

+5-10
Original file line numberDiff line numberDiff line change
@@ -240,16 +240,11 @@ export class Organization extends BaseResource implements OrganizationResource {
240240
__experimental_getSubscriptions = async (
241241
getSubscriptionsParams?: __experimental_GetSubscriptionsParams,
242242
): Promise<ClerkPaginatedResponse<__experimental_CommerceSubscriptionResource>> => {
243-
return await BaseResource._fetch(
244-
{
245-
path: `/organizations/${this.id}/subscriptions`,
246-
method: 'GET',
247-
search: convertPageToOffsetSearchParams(getSubscriptionsParams),
248-
},
249-
{
250-
forceUpdateClient: true,
251-
},
252-
).then(res => {
243+
return await BaseResource._fetch({
244+
path: `/organizations/${this.id}/subscriptions`,
245+
method: 'GET',
246+
search: convertPageToOffsetSearchParams(getSubscriptionsParams),
247+
}).then(res => {
253248
const { data: subscriptions, total_count } =
254249
res?.response as unknown as ClerkPaginatedResponse<__experimental_CommerceSubscriptionJSON>;
255250

packages/clerk-js/src/ui/Components.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ const Components = (props: ComponentsProps) => {
541541
<Checkout
542542
planId={checkoutDrawer.props.planId}
543543
planPeriod={checkoutDrawer.props.planPeriod}
544-
orgId={checkoutDrawer.props.orgId}
544+
subscriberType={checkoutDrawer.props.subscriberType}
545545
onSubscriptionComplete={checkoutDrawer.props.onSubscriptionComplete}
546546
/>
547547
</LazyDrawerRenderer>

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

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const __experimental_Checkout = (props: __experimental_CheckoutProps) =>
1212
<__experimental_CheckoutContext.Provider
1313
value={{
1414
componentName: 'Checkout',
15+
...props,
1516
}}
1617
>
1718
<Drawer.Content>

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

+28-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useClerk } from '@clerk/shared/react';
1+
import { useClerk, useOrganization } from '@clerk/shared/react';
22
import type {
33
__experimental_CommerceCheckoutResource,
44
__experimental_CommerceMoney,
@@ -8,6 +8,7 @@ import type {
88
} from '@clerk/types';
99
import { useMemo, useState } from 'react';
1010

11+
import { __experimental_PaymentSourcesContext, useCheckoutContext } from '../../contexts';
1112
import { Box, Button, Col, descriptors, Flex, Form, Icon, localizationKeys, Text } from '../../customizables';
1213
import { Alert, Disclosure, Divider, Drawer, LineItems, Select, SelectButton, SelectOptionList } from '../../elements';
1314
import { useFetch } from '../../hooks';
@@ -90,17 +91,29 @@ const CheckoutFormElements = ({
9091
onCheckoutComplete: (checkout: __experimental_CommerceCheckoutResource) => void;
9192
}) => {
9293
const { __experimental_commerce } = useClerk();
94+
const { organization } = useOrganization();
95+
const { subscriberType } = useCheckoutContext();
9396
const [openAccountFundsDropDown, setOpenAccountFundsDropDown] = useState(true);
9497
const [openAddNewSourceDropDown, setOpenAddNewSourceDropDown] = useState(true);
9598
const [isSubmitting, setIsSubmitting] = useState(false);
9699
const [submitError, setSubmitError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
97100

98-
const { data } = useFetch(__experimental_commerce?.getPaymentSources, 'commerce-payment-sources');
101+
const { data } = useFetch(
102+
__experimental_commerce?.getPaymentSources,
103+
{
104+
...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
105+
},
106+
undefined,
107+
'commerce-payment-sources',
108+
);
99109
const { data: paymentSources } = data || { data: [] };
100110

101111
const confirmCheckout = async ({ paymentSourceId }: { paymentSourceId: string }) => {
102112
return checkout
103-
.confirm({ paymentSourceId })
113+
.confirm({
114+
paymentSourceId,
115+
...(subscriberType === 'org' ? { orgId: organization?.id } : {}),
116+
})
104117
.then(newCheckout => {
105118
onCheckoutComplete(newCheckout);
106119
})
@@ -176,16 +189,18 @@ const CheckoutFormElements = ({
176189
{/* TODO(@Commerce): needs localization */}
177190
<Disclosure.Trigger text='Add a New Payment Source' />
178191
<Disclosure.Content>
179-
<AddPaymentSource
180-
checkout={checkout}
181-
onSuccess={onAddPaymentSourceSuccess}
182-
submitLabel={localizationKeys(
183-
'userProfile.__experimental_billingPage.paymentSourcesSection.formButtonPrimary__pay',
184-
{
185-
amount: `${(checkout.totals.totalDueNow || checkout.totals.grandTotal).currencySymbol}${(checkout.totals.totalDueNow || checkout.totals.grandTotal).amountFormatted}`,
186-
},
187-
)}
188-
/>
192+
<__experimental_PaymentSourcesContext.Provider value={{ componentName: 'PaymentSources', subscriberType }}>
193+
<AddPaymentSource
194+
checkout={checkout}
195+
onSuccess={onAddPaymentSourceSuccess}
196+
submitLabel={localizationKeys(
197+
'userProfile.__experimental_billingPage.paymentSourcesSection.formButtonPrimary__pay',
198+
{
199+
amount: `${(checkout.totals.totalDueNow || checkout.totals.grandTotal).currencySymbol}${(checkout.totals.totalDueNow || checkout.totals.grandTotal).amountFormatted}`,
200+
},
201+
)}
202+
/>
203+
</__experimental_PaymentSourcesContext.Provider>
189204
</Disclosure.Content>
190205
</Disclosure.Root>
191206
</Col>

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { CheckoutComplete } from './CheckoutComplete';
66
import { CheckoutForm } from './CheckoutForm';
77

88
export const CheckoutPage = (props: __experimental_CheckoutProps) => {
9-
const { planId, planPeriod, orgId, onSubscriptionComplete } = props;
9+
const { planId, planPeriod, subscriberType, onSubscriptionComplete } = props;
1010

1111
const { checkout, updateCheckout, isLoading } = useCheckout({
1212
planId,
1313
planPeriod,
14-
orgId,
14+
subscriberType,
1515
});
1616

1717
const onCheckoutComplete = (newCheckout: __experimental_CommerceCheckoutResource) => {

packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationBillingPage.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { __experimental_PricingTableContext } from '../../contexts';
1+
import { __experimental_PaymentSourcesContext, __experimental_PricingTableContext } from '../../contexts';
22
import { Col, descriptors, localizationKeys } from '../../customizables';
33
import {
44
Card,
@@ -11,6 +11,7 @@ import {
1111
useCardState,
1212
withCardStateProvider,
1313
} from '../../elements';
14+
import { __experimental_PaymentSources } from '../PaymentSources/PaymentSources';
1415
import { __experimental_PricingTable } from '../PricingTable';
1516

1617
export const OrganizationBillingPage = withCardStateProvider(() => {
@@ -58,7 +59,13 @@ export const OrganizationBillingPage = withCardStateProvider(() => {
5859
</__experimental_PricingTableContext.Provider>
5960
</TabPanel>
6061
<TabPanel sx={{ width: '100%' }}>Invoices</TabPanel>
61-
<TabPanel sx={{ width: '100%' }}>Payment Sources</TabPanel>
62+
<TabPanel sx={{ width: '100%' }}>
63+
<__experimental_PaymentSourcesContext.Provider
64+
value={{ componentName: 'PaymentSources', subscriberType: 'org' }}
65+
>
66+
<__experimental_PaymentSources />
67+
</__experimental_PaymentSourcesContext.Provider>
68+
</TabPanel>
6269
</TabPanels>
6370
</Tabs>
6471
</Col>

0 commit comments

Comments
 (0)