From b2f4546db3c56e3630f0cfc4007058f632050f80 Mon Sep 17 00:00:00 2001 From: nikospapcom Date: Mon, 5 May 2025 16:06:14 +0300 Subject: [PATCH] feat(clerk-js,localizations,types): Add `Pay with test card` button on `` component --- .changeset/tiny-wolves-battle.md | 7 + packages/clerk-js/bundlewatch.config.json | 4 +- .../components/Checkout/CheckoutComplete.tsx | 4 +- .../ui/components/Checkout/CheckoutForm.tsx | 23 +- .../PaymentSources/AddPaymentSource.tsx | 204 +++++++++++++----- packages/localizations/src/ar-SA.ts | 1 + packages/localizations/src/be-BY.ts | 1 + packages/localizations/src/bg-BG.ts | 1 + packages/localizations/src/ca-ES.ts | 1 + packages/localizations/src/cs-CZ.ts | 1 + packages/localizations/src/da-DK.ts | 1 + packages/localizations/src/de-DE.ts | 1 + packages/localizations/src/el-GR.ts | 1 + packages/localizations/src/en-GB.ts | 1 + packages/localizations/src/en-US.ts | 1 + packages/localizations/src/es-ES.ts | 1 + packages/localizations/src/es-MX.ts | 1 + packages/localizations/src/fi-FI.ts | 1 + packages/localizations/src/fr-FR.ts | 1 + packages/localizations/src/he-IL.ts | 1 + packages/localizations/src/hr-HR.ts | 1 + packages/localizations/src/hu-HU.ts | 1 + packages/localizations/src/id-ID.ts | 1 + packages/localizations/src/is-IS.ts | 1 + packages/localizations/src/it-IT.ts | 1 + packages/localizations/src/ja-JP.ts | 1 + packages/localizations/src/ko-KR.ts | 1 + packages/localizations/src/mn-MN.ts | 1 + packages/localizations/src/nb-NO.ts | 1 + packages/localizations/src/nl-BE.ts | 1 + packages/localizations/src/nl-NL.ts | 1 + packages/localizations/src/pl-PL.ts | 1 + packages/localizations/src/pt-BR.ts | 1 + packages/localizations/src/pt-PT.ts | 1 + packages/localizations/src/ro-RO.ts | 1 + packages/localizations/src/ru-RU.ts | 1 + packages/localizations/src/sk-SK.ts | 1 + packages/localizations/src/sr-RS.ts | 1 + packages/localizations/src/sv-SE.ts | 1 + packages/localizations/src/th-TH.ts | 1 + packages/localizations/src/tr-TR.ts | 1 + packages/localizations/src/uk-UA.ts | 1 + packages/localizations/src/vi-VN.ts | 1 + packages/localizations/src/zh-CN.ts | 1 + packages/localizations/src/zh-TW.ts | 1 + .../unstable/page-objects/checkout.ts | 5 +- packages/types/src/commerce.ts | 4 + packages/types/src/localization.ts | 1 + 48 files changed, 231 insertions(+), 61 deletions(-) create mode 100644 .changeset/tiny-wolves-battle.md diff --git a/.changeset/tiny-wolves-battle.md b/.changeset/tiny-wolves-battle.md new file mode 100644 index 00000000000..54e1780941e --- /dev/null +++ b/.changeset/tiny-wolves-battle.md @@ -0,0 +1,7 @@ +--- +'@clerk/localizations': patch +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +Add `Pay with test card` button on `` component in dev instance diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index baab3fe9030..09f6fd140cf 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -21,8 +21,8 @@ { "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": "5.75KB" }, - { "path": "./dist/paymentSources*.js", "maxSize": "8.9KB" }, + { "path": "./dist/checkout*.js", "maxSize": "5.79KB" }, + { "path": "./dist/paymentSources*.js", "maxSize": "9.06KB" }, { "path": "./dist/up-billing-page*.js", "maxSize": "2.4KB" }, { "path": "./dist/op-billing-page*.js", "maxSize": "2.4KB" }, { "path": "./dist/sessionTasks*.js", "maxSize": "1KB" } diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx index d1f29af89b8..2f3d916e693 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx @@ -257,7 +257,9 @@ export const CheckoutComplete = ({ text={ checkout.totals.totalDueNow.amount > 0 ? checkout.paymentSource - ? `${capitalize(checkout.paymentSource.cardType)} ⋯ ${checkout.paymentSource.last4}` + ? checkout.paymentSource.paymentMethod !== 'card' + ? `${capitalize(checkout.paymentSource.paymentMethod)}` + : `${capitalize(checkout.paymentSource.cardType)} ⋯ ${checkout.paymentSource.last4}` : '–' : checkout.subscription?.periodStart ? formatDate(new Date(checkout.subscription.periodStart)) diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx index b7be35a3351..2f5eab35547 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx @@ -169,6 +169,20 @@ const CheckoutFormElements = ({ setIsSubmitting(false); }; + const onPayWithTestPaymentSourceSuccess = async () => { + try { + const newCheckout = await checkout.confirm({ + gateway: 'stripe', + useTestCard: true, + ...(subscriberType === 'org' ? { orgId: organization?.id } : {}), + }); + onCheckoutComplete(newCheckout); + void revalidatePaymentSources(); + } catch (error) { + handleError(error, [], setSubmitError); + } + }; + return ( 0 @@ -225,6 +240,7 @@ const CheckoutFormElements = ({ } submitError={submitError} setSubmitError={setSubmitError} + showPayWithTestCardSection /> )} @@ -252,9 +268,14 @@ const ExistingPaymentSourceForm = ({ const options = useMemo(() => { return paymentSources.map(source => { + const label = + source.paymentMethod !== 'card' + ? `${capitalize(source.paymentMethod)}` + : `${capitalize(source.cardType)} ⋯ ${source.last4}`; + return { value: source.id, - label: `${capitalize(source.cardType)} ⋯ ${source.last4}`, + label, }; }); }, [paymentSources]); diff --git a/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx b/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx index 7d14576de16..7a21331b9c4 100644 --- a/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx +++ b/packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx @@ -7,7 +7,7 @@ import { useEffect, useRef, useState } from 'react'; import { clerkUnsupportedEnvironmentWarning } from '../../../core/errors'; import { useEnvironment, useSubscriberTypeContext } from '../../contexts'; -import { Box, descriptors, Flex, localizationKeys, Spinner, Text, useAppearance } from '../../customizables'; +import { Box, Button, descriptors, Flex, localizationKeys, Spinner, Text, useAppearance } from '../../customizables'; import { Alert, Form, FormButtons, FormContainer, LineItems, withCardStateProvider } from '../../elements'; import { useFetch } from '../../hooks/useFetch'; import type { LocalizationKey } from '../../localization'; @@ -22,10 +22,21 @@ type AddPaymentSourceProps = { submitError?: ClerkRuntimeError | ClerkAPIError | string | undefined; setSubmitError?: (submitError: ClerkRuntimeError | ClerkAPIError | string | undefined) => void; resetStripeElements?: () => void; + onPayWithTestPaymentSourceSuccess?: () => void; + showPayWithTestCardSection?: boolean; }; export const AddPaymentSource = (props: AddPaymentSourceProps) => { - const { checkout, submitLabel, onSuccess, cancelAction, submitError, setSubmitError } = props; + const { + checkout, + submitLabel, + onSuccess, + cancelAction, + submitError, + setSubmitError, + onPayWithTestPaymentSourceSuccess, + showPayWithTestCardSection, + } = props; const { __experimental_commerce } = useClerk(); const { __experimental_commerceSettings } = useEnvironment(); const { organization } = useOrganization(); @@ -137,6 +148,8 @@ export const AddPaymentSource = (props: AddPaymentSourceProps) => { submitError={submitError} setSubmitError={setSubmitError} resetStripeElements={resetStripeElements} + onPayWithTestPaymentSourceSuccess={onPayWithTestPaymentSourceSuccess} + showPayWithTestCardSection={showPayWithTestCardSection} /> ); @@ -151,6 +164,8 @@ const AddPaymentSourceForm = withCardStateProvider( submitError, setSubmitError, resetStripeElements, + onPayWithTestPaymentSourceSuccess, + showPayWithTestCardSection, }: AddPaymentSourceProps) => { const clerk = useClerk(); const stripe = useStripe(); @@ -218,68 +233,72 @@ const AddPaymentSourceForm = withCardStateProvider( rowGap: t.space.$3, })} > - {clerk.instanceType === 'development' && ( - ({ - background: t.colors.$neutralAlpha50, - padding: t.space.$2x5, - borderRadius: t.radii.$md, - borderWidth: t.borderWidths.$normal, - borderStyle: t.borderStyles.$solid, - borderColor: t.colors.$neutralAlpha100, - display: 'flex', - flexDirection: 'column', - rowGap: t.space.$2, - position: 'relative', - })} - > + {showPayWithTestCardSection ? ( + + ) : ( + clerk.instanceType === 'development' && ( ({ - position: 'absolute', - inset: 0, - background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, - maskImage: `linear-gradient(transparent 20%, black)`, - pointerEvents: 'none', - })} - /> - - - Test card information - - ({ - color: t.colors.$warning500, - fontWeight: t.fontWeights.$semibold, + position: 'absolute', + inset: 0, + background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, + maskImage: `linear-gradient(transparent 20%, black)`, + pointerEvents: 'none', })} + /> + - Development mode - + + Test card information + + ({ + color: t.colors.$warning500, + fontWeight: t.fontWeights.$semibold, + })} + > + Development mode + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + ) )} void }) => { + const clerk = useClerk(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const onPaymentSourceSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + onCheckoutComplete?.(); + }; + + if (clerk.instanceType !== 'development') { + return null; + } + + return ( + ({ + background: t.colors.$neutralAlpha50, + padding: t.space.$2x5, + borderRadius: t.radii.$md, + borderWidth: t.borderWidths.$normal, + borderStyle: t.borderStyles.$solid, + borderColor: t.colors.$neutralAlpha100, + display: 'flex', + flexDirection: 'column', + rowGap: t.space.$2, + position: 'relative', + })} + > + ({ + position: 'absolute', + inset: 0, + background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, + maskImage: `linear-gradient(transparent 20%, black)`, + pointerEvents: 'none', + })} + /> + ({ + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + rowGap: t.space.$2, + })} + > + ({ + color: t.colors.$warning500, + fontWeight: t.fontWeights.$semibold, + })} + > + Development mode + +