@@ -15,6 +15,7 @@ import { DevOnly } from '../../common/DevOnly';
1515import { useCheckoutContext , useEnvironment , usePaymentMethods } from '../../contexts' ;
1616import { Box , Button , Col , descriptors , Flex , Form , localizationKeys , Spinner , Text } from '../../customizables' ;
1717import { ChevronUpDown , InformationCircle } from '../../icons' ;
18+ import type { PropsOfComponent , ThemableCssProp } from '../../styledSystem' ;
1819import * as AddPaymentMethod from '../PaymentMethods/AddPaymentMethod' ;
1920import { PaymentMethodRow } from '../PaymentMethods/PaymentMethodRow' ;
2021import { SubscriptionBadge } from '../Subscriptions/badge' ;
@@ -138,7 +139,7 @@ export const CheckoutForm = withCardStateProvider(() => {
138139} ) ;
139140
140141const useCheckoutMutations = ( ) => {
141- const { for : _for , onSubscriptionComplete } = useCheckoutContext ( ) ;
142+ const { onSubscriptionComplete } = useCheckoutContext ( ) ;
142143 const { checkout } = useCheckout ( ) ;
143144 const { id, confirm } = checkout ;
144145 const card = useCardState ( ) ;
@@ -172,6 +173,11 @@ const useCheckoutMutations = () => {
172173 } ) ;
173174 } ;
174175
176+ const payWithoutPaymentMethod = ( e : React . FormEvent < HTMLFormElement > ) => {
177+ e . preventDefault ( ) ;
178+ return confirmCheckout ( { } ) ;
179+ } ;
180+
175181 const addPaymentMethodAndPay = ( ctx : { gateway : 'stripe' ; paymentToken : string } ) => confirmCheckout ( ctx ) ;
176182
177183 const payWithTestCard = ( ) =>
@@ -184,6 +190,7 @@ const useCheckoutMutations = () => {
184190 payWithExistingPaymentMethod,
185191 addPaymentMethodAndPay,
186192 payWithTestCard,
193+ payWithoutPaymentMethod,
187194 } ;
188195} ;
189196
@@ -220,13 +227,9 @@ const CheckoutFormElementsInternal = () => {
220227 paymentMethods . length > 0 || __BUILD_DISABLE_RHC__ ? 'existing' : 'new' ,
221228 ) ;
222229
223- // Check if payment methods should be shown based on:
224- // 1. Immediate plan change (not a downgrade)
225- // 2. Either there's an amount due now OR it's a free trial that requires payment method
226- const showPaymentMethods =
227- isImmediatePlanChange &&
228- ( totals . totalDueNow . amount > 0 ||
229- ( ! ! freeTrialEndsAt && environment . commerceSettings . billing . freeTrialRequiresPaymentMethod ) ) ;
230+ const isFreeTrial = Boolean ( freeTrialEndsAt ) ;
231+ const showTabs = isImmediatePlanChange && ( totals . totalDueNow . amount > 0 || isFreeTrial ) ;
232+ const needsPaymentMethod = ! ( isFreeTrial && ! environment . commerceSettings . billing . freeTrialRequiresPaymentMethod ) ;
230233
231234 if ( ! id ) {
232235 return null ;
@@ -240,7 +243,7 @@ const CheckoutFormElementsInternal = () => {
240243 >
241244 { __BUILD_DISABLE_RHC__ ? null : (
242245 < >
243- { paymentMethods . length > 0 && showPaymentMethods && (
246+ { paymentMethods . length > 0 && showTabs && needsPaymentMethod && (
244247 < SegmentedControl . Root
245248 aria-label = 'Payment method source'
246249 value = { paymentMethodSource }
@@ -261,18 +264,17 @@ const CheckoutFormElementsInternal = () => {
261264 </ >
262265 ) }
263266
264- { showPaymentMethods && paymentMethodSource === 'existing' && (
265- < ExistingPaymentMethodForm
266- paymentMethods = { paymentMethods }
267- totalDueNow = { totals . totalDueNow }
268- />
269- ) }
270-
271- { __BUILD_DISABLE_RHC__
272- ? null
273- : showPaymentMethods && paymentMethodSource === 'new' && < AddPaymentMethodForCheckout /> }
267+ { paymentMethodSource === 'existing' &&
268+ ( needsPaymentMethod ? (
269+ < ExistingPaymentMethodForm
270+ paymentMethods = { paymentMethods }
271+ totalDueNow = { totals . totalDueNow }
272+ />
273+ ) : (
274+ < FreeTrialButton />
275+ ) ) }
274276
275- { ! showPaymentMethods && < FreeTrialButton /> }
277+ { __BUILD_DISABLE_RHC__ ? null : paymentMethodSource === 'new' && < AddPaymentMethodForCheckout /> }
276278 </ Col >
277279 ) ;
278280} ;
@@ -356,52 +358,17 @@ const useSubmitLabel = () => {
356358} ;
357359
358360const FreeTrialButton = withCardStateProvider ( ( ) => {
359- const { for : _for , onSubscriptionComplete } = useCheckoutContext ( ) ;
360- const submitLabel = useSubmitLabel ( ) ;
361+ const { for : _for } = useCheckoutContext ( ) ;
362+ const { payWithoutPaymentMethod } = useCheckoutMutations ( ) ;
361363 const card = useCardState ( ) ;
362- const { checkout } = useCheckout ( ) ;
363-
364- const handleFreeTrialStart = async ( ) => {
365- card . setLoading ( ) ;
366- card . setError ( undefined ) ;
367-
368- try {
369- // For free trials without payment method requirement, we can confirm without payment details
370- const { data, error } = await checkout . confirm ( { } ) ;
371-
372- if ( error ) {
373- handleError ( error , [ ] , card . setError ) ;
374- } else if ( data ) {
375- onSubscriptionComplete ?.( ) ;
376- }
377- } catch ( error ) {
378- handleError ( error , [ ] , card . setError ) ;
379- } finally {
380- card . setIdle ( ) ;
381- }
382- } ;
383364
384365 return (
385366 < Form
386- sx = { t => ( {
387- display : 'flex' ,
388- flexDirection : 'column' ,
389- rowGap : t . space . $4 ,
390- } ) }
367+ onSubmit = { payWithoutPaymentMethod }
368+ sx = { formProps }
391369 >
392370 < Card . Alert > { card . error } </ Card . Alert >
393- < Button
394- type = 'button'
395- colorScheme = 'primary'
396- size = 'sm'
397- textVariant = { 'buttonLarge' }
398- sx = { {
399- width : '100%' ,
400- } }
401- isLoading = { card . isLoading }
402- localizationKey = { submitLabel }
403- onClick = { handleFreeTrialStart }
404- />
371+ < CheckoutSubmitButton />
405372 </ Form >
406373 ) ;
407374} ) ;
@@ -425,6 +392,32 @@ const AddPaymentMethodForCheckout = withCardStateProvider(() => {
425392 ) ;
426393} ) ;
427394
395+ const CheckoutSubmitButton = ( props : PropsOfComponent < typeof Button > ) => {
396+ const card = useCardState ( ) ;
397+ const submitLabel = useSubmitLabel ( ) ;
398+
399+ return (
400+ < Button
401+ type = 'submit'
402+ colorScheme = 'primary'
403+ size = 'sm'
404+ textVariant = { 'buttonLarge' }
405+ sx = { {
406+ width : '100%' ,
407+ } }
408+ isLoading = { card . isLoading }
409+ localizationKey = { submitLabel }
410+ { ...props }
411+ />
412+ ) ;
413+ } ;
414+
415+ const formProps : ThemableCssProp = t => ( {
416+ display : 'flex' ,
417+ flexDirection : 'column' ,
418+ rowGap : t . space . $4 ,
419+ } ) ;
420+
428421const ExistingPaymentMethodForm = withCardStateProvider (
429422 ( {
430423 totalDueNow,
@@ -433,7 +426,6 @@ const ExistingPaymentMethodForm = withCardStateProvider(
433426 totalDueNow : BillingMoneyAmount ;
434427 paymentMethods : BillingPaymentMethodResource [ ] ;
435428 } ) => {
436- const submitLabel = useSubmitLabel ( ) ;
437429 const { checkout } = useCheckout ( ) ;
438430 const { paymentMethod, isImmediatePlanChange, freeTrialEndsAt } = checkout ;
439431 const environment = useEnvironment ( ) ;
@@ -466,11 +458,7 @@ const ExistingPaymentMethodForm = withCardStateProvider(
466458 return (
467459 < Form
468460 onSubmit = { payWithExistingPaymentMethod }
469- sx = { t => ( {
470- display : 'flex' ,
471- flexDirection : 'column' ,
472- rowGap : t . space . $4 ,
473- } ) }
461+ sx = { formProps }
474462 >
475463 { showPaymentMethods ? (
476464 < Select
@@ -513,17 +501,7 @@ const ExistingPaymentMethodForm = withCardStateProvider(
513501 />
514502 ) }
515503 < Card . Alert > { card . error } </ Card . Alert >
516- < Button
517- type = 'submit'
518- colorScheme = 'primary'
519- size = 'sm'
520- textVariant = { 'buttonLarge' }
521- sx = { {
522- width : '100%' ,
523- } }
524- isLoading = { card . isLoading }
525- localizationKey = { submitLabel }
526- />
504+ < CheckoutSubmitButton />
527505 </ Form >
528506 ) ;
529507 } ,
0 commit comments