From e1c53d5ba3655b47754ae62c56ecf711769f4260 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Mon, 28 Apr 2025 17:35:45 +0300 Subject: [PATCH 1/3] feat(commerce): Checkout confirm request handles both new/existing payment sources --- .changeset/breezy-hairs-smell.md | 6 ++++++ .../ui/components/Checkout/CheckoutForm.tsx | 19 +++++++++++++----- .../PaymentSources/AddPaymentSource.tsx | 20 ++++--------------- .../PaymentSources/PaymentSources.tsx | 13 ++++++++++-- packages/types/src/commerce.ts | 19 ++++++++++++------ 5 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 .changeset/breezy-hairs-smell.md diff --git a/.changeset/breezy-hairs-smell.md b/.changeset/breezy-hairs-smell.md new file mode 100644 index 00000000000..a5866cb2ccd --- /dev/null +++ b/.changeset/breezy-hairs-smell.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +Checkout confirm request handles both new/existing payment sources diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx index 4a3b96698c4..9317a708e5a 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx @@ -3,9 +3,11 @@ import type { __experimental_CommerceCheckoutResource, __experimental_CommerceMoney, __experimental_CommercePaymentSourceResource, + __experimental_ConfirmCheckoutParams, ClerkAPIError, ClerkRuntimeError, } from '@clerk/types'; +import type { SetupIntent } from '@stripe/stripe-js'; import { useMemo, useState } from 'react'; import { useCheckoutContext } from '../../contexts'; @@ -111,10 +113,10 @@ const CheckoutFormElements = ({ const { data: paymentSources } = data || { data: [] }; - const confirmCheckout = async ({ paymentSourceId }: { paymentSourceId: string }) => { + const confirmCheckout = async (params: __experimental_ConfirmCheckoutParams) => { try { const newCheckout = await checkout.confirm({ - paymentSourceId, + ...params, ...(subscriberType === 'org' ? { orgId: organization?.id } : {}), }); onCheckoutComplete(newCheckout); @@ -131,12 +133,19 @@ const CheckoutFormElements = ({ const data = new FormData(e.currentTarget); const paymentSourceId = data.get('payment_source_id') as string; - await confirmCheckout({ paymentSourceId }); + await confirmCheckout({ + paymentSourceId, + ...(subscriberType === 'org' ? { orgId: organization?.id } : {}), + }); setIsSubmitting(false); }; - const onAddPaymentSourceSuccess = async (paymentSource: __experimental_CommercePaymentSourceResource) => { - await confirmCheckout({ paymentSourceId: paymentSource.id }); + const onAddPaymentSourceSuccess = async (ctx: { stripeSetupIntent?: SetupIntent }) => { + await confirmCheckout({ + gateway: 'stripe', + paymentToken: ctx.stripeSetupIntent?.payment_method as string, + ...(subscriberType === 'org' ? { orgId: organization?.id } : {}), + }); setIsSubmitting(false); }; diff --git a/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx b/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx index ef6b87379dc..bf283a28d5a 100644 --- a/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx +++ b/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx @@ -1,12 +1,7 @@ import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; -import type { - __experimental_CommerceCheckoutResource, - __experimental_CommercePaymentSourceResource, - ClerkAPIError, - ClerkRuntimeError, -} from '@clerk/types'; +import type { __experimental_CommerceCheckoutResource, ClerkAPIError, ClerkRuntimeError } from '@clerk/types'; import { Elements, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'; -import type { Appearance as StripeAppearance, Stripe } from '@stripe/stripe-js'; +import type { Appearance as StripeAppearance, SetupIntent, Stripe } from '@stripe/stripe-js'; import { loadStripe } from '@stripe/stripe-js'; import { useEffect, useRef, useState } from 'react'; @@ -20,7 +15,7 @@ import { animations } from '../../styledSystem'; import { handleError, normalizeColorString } from '../../utils'; type AddPaymentSourceProps = { - onSuccess: (paymentSource: __experimental_CommercePaymentSourceResource) => Promise; + onSuccess: (context: { stripeSetupIntent?: SetupIntent }) => Promise; checkout?: __experimental_CommerceCheckoutResource; submitLabel?: LocalizationKey; cancelAction?: () => void; @@ -136,7 +131,6 @@ export const AddPaymentSource = (props: AddPaymentSourceProps) => { const AddPaymentSourceForm = withCardStateProvider( ({ submitLabel, onSuccess, cancelAction, checkout }: AddPaymentSourceProps) => { - const { __experimental_commerce } = useClerk(); const stripe = useStripe(); const elements = useElements(); const { displayConfig } = useEnvironment(); @@ -174,15 +168,9 @@ const AddPaymentSourceForm = withCardStateProvider( return; // just return, since stripe will handle the error } - const paymentSource = await __experimental_commerce.addPaymentSource({ - gateway: 'stripe', - paymentToken: setupIntent.payment_method as string, - ...(subscriberType === 'org' ? { orgId: organization?.id } : {}), - }); + await onSuccess({ stripeSetupIntent: setupIntent }); revalidate(); - - void onSuccess(paymentSource); } catch (error) { void handleError(error, [], setSubmitError); } diff --git a/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx b/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx index 978428cf925..2cf85a35bb9 100644 --- a/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx +++ b/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx @@ -1,5 +1,6 @@ import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; -import type { __experimental_CommercePaymentSourceResource } from '@clerk/types'; +import type { __experimental_CommercePaymentSourceResource, __experimental_PaymentSourcesProps } from '@clerk/types'; +import type { SetupIntent } from '@stripe/stripe-js'; import { Fragment, useRef } from 'react'; import { RemoveResourceForm } from '../../common'; @@ -15,8 +16,16 @@ import { PaymentSourceRow } from './PaymentSourceRow'; const AddScreen = ({ onSuccess }: { onSuccess: () => void }) => { const { close } = useActionContext(); + const { __experimental_commerce } = useClerk(); + const { subscriberType } = usePaymentSourcesContext(); + const { organization } = useOrganization(); - const onAddPaymentSourceSuccess = (_: __experimental_CommercePaymentSourceResource) => { + const onAddPaymentSourceSuccess = async (context: { stripeSetupIntent?: SetupIntent }) => { + await __experimental_commerce.addPaymentSource({ + gateway: 'stripe', + paymentToken: context.stripeSetupIntent?.payment_method as string, + ...(subscriberType === 'org' ? { orgId: organization?.id } : {}), + }); onSuccess(); close(); return Promise.resolve(); diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts index 95a76a40da9..3f66d233fcc 100644 --- a/packages/types/src/commerce.ts +++ b/packages/types/src/commerce.ts @@ -77,12 +77,14 @@ export type __experimental_CommercePaymentSourceStatus = 'active' | 'expired' | export type __experimental_GetPaymentSourcesParams = WithOptionalOrgType; +export type __experimental_PaymentGateway = 'stripe' | 'paypal'; + export type __experimental_InitializePaymentSourceParams = WithOptionalOrgType<{ - gateway: 'stripe' | 'paypal'; + gateway: __experimental_PaymentGateway; }>; export type __experimental_AddPaymentSourceParams = WithOptionalOrgType<{ - gateway: 'stripe' | 'paypal'; + gateway: __experimental_PaymentGateway; paymentToken: string; }>; @@ -156,10 +158,15 @@ export type __experimental_CreateCheckoutParams = WithOptionalOrgType<{ planPeriod: __experimental_CommerceSubscriptionPlanPeriod; }>; -export type __experimental_ConfirmCheckoutParams = WithOptionalOrgType<{ - paymentSourceId?: string; -}>; - +export type __experimental_ConfirmCheckoutParams = WithOptionalOrgType< + | { + paymentSourceId?: string; + } + | { + paymentToken?: string; + gateway?: __experimental_PaymentGateway; + } +>; export interface __experimental_CommerceCheckoutResource extends ClerkResource { id: string; externalClientSecret: string; From 68852867f8bf19ef2c06b1bd22f531b35af4132f Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Mon, 28 Apr 2025 20:48:06 +0300 Subject: [PATCH 2/3] chore(clerk-js): Resolve conflicts from previous rebase --- .../src/ui/components/PaymentSources/PaymentSources.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx b/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx index 2cf85a35bb9..433a4314a72 100644 --- a/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx +++ b/packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx @@ -1,5 +1,5 @@ import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; -import type { __experimental_CommercePaymentSourceResource, __experimental_PaymentSourcesProps } from '@clerk/types'; +import type { __experimental_CommercePaymentSourceResource } from '@clerk/types'; import type { SetupIntent } from '@stripe/stripe-js'; import { Fragment, useRef } from 'react'; @@ -17,7 +17,7 @@ import { PaymentSourceRow } from './PaymentSourceRow'; const AddScreen = ({ onSuccess }: { onSuccess: () => void }) => { const { close } = useActionContext(); const { __experimental_commerce } = useClerk(); - const { subscriberType } = usePaymentSourcesContext(); + const subscriberType = useSubscriberTypeContext(); const { organization } = useOrganization(); const onAddPaymentSourceSuccess = async (context: { stripeSetupIntent?: SetupIntent }) => { From 3814494bc74764c31381a2fc653af3a671dc0f39 Mon Sep 17 00:00:00 2001 From: Vaggelis Yfantis Date: Mon, 28 Apr 2025 21:09:53 +0300 Subject: [PATCH 3/3] chore(clerk-js): Update bundlewatch --- packages/clerk-js/bundlewatch.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index aed30b03f3c..2e1811b9a23 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -21,7 +21,7 @@ { "path": "./dist/waitlist*.js", "maxSize": "1.3KB" }, { "path": "./dist/keylessPrompt*.js", "maxSize": "6.5KB" }, { "path": "./dist/pricingTable*.js", "maxSize": "4.02KB" }, - { "path": "./dist/checkout*.js", "maxSize": "4.9KB" }, + { "path": "./dist/checkout*.js", "maxSize": "4.92KB" }, { "path": "./dist/paymentSources*.js", "maxSize": "8.5KB" }, { "path": "./dist/up-billing-page*.js", "maxSize": "1.78KB" }, { "path": "./dist/op-billing-page*.js", "maxSize": "1.76KB" },