@@ -465,4 +465,140 @@ describe('Checkout', () => {
465465 expect ( getByText ( 'August 19, 2025' ) ) . toBeVisible ( ) ;
466466 } ) ;
467467 } ) ;
468+
469+ it ( 'renders existing payment sources during checkout confirmation' , async ( ) => {
470+ const { wrapper, fixtures } = await createFixtures ( f => {
471+ f . withUser ( { email_addresses :
[ '[email protected] ' ] } ) ; 472+ } ) ;
473+
474+ fixtures . clerk . user ?. getPaymentSources . mockResolvedValue ( {
475+ data : [
476+ {
477+ id : 'pm_test_visa' ,
478+ last4 : '4242' ,
479+ paymentMethod : 'card' ,
480+ cardType : 'visa' ,
481+ isDefault : true ,
482+ isRemovable : true ,
483+ status : 'active' ,
484+ walletType : undefined ,
485+ remove : jest . fn ( ) ,
486+ makeDefault : jest . fn ( ) ,
487+ pathRoot : '/' ,
488+ reload : jest . fn ( ) ,
489+ } ,
490+ {
491+ id : 'pm_test_mastercard' ,
492+ last4 : '5555' ,
493+ paymentMethod : 'card' ,
494+ cardType : 'mastercard' ,
495+ isDefault : false ,
496+ isRemovable : true ,
497+ status : 'active' ,
498+ walletType : undefined ,
499+ remove : jest . fn ( ) ,
500+ makeDefault : jest . fn ( ) ,
501+ pathRoot : '/' ,
502+ reload : jest . fn ( ) ,
503+ } ,
504+ ] ,
505+ total_count : 2 ,
506+ } ) ;
507+
508+ fixtures . clerk . billing . startCheckout . mockResolvedValue ( {
509+ id : 'chk_trial_2' ,
510+ status : 'needs_confirmation' ,
511+ externalClientSecret : 'cs_test_trial_2' ,
512+ externalGatewayId : 'gw_test' ,
513+ totals : {
514+ subtotal : { amount : 1000 , amountFormatted : '10.00' , currency : 'USD' , currencySymbol : '$' } ,
515+ grandTotal : { amount : 1000 , amountFormatted : '10.00' , currency : 'USD' , currencySymbol : '$' } ,
516+ taxTotal : { amount : 0 , amountFormatted : '0.00' , currency : 'USD' , currencySymbol : '$' } ,
517+ credit : { amount : 0 , amountFormatted : '0.00' , currency : 'USD' , currencySymbol : '$' } ,
518+ pastDue : { amount : 0 , amountFormatted : '0.00' , currency : 'USD' , currencySymbol : '$' } ,
519+ totalDueNow : { amount : 0 , amountFormatted : '0.00' , currency : 'USD' , currencySymbol : '$' } ,
520+ } ,
521+ isImmediatePlanChange : true ,
522+ planPeriod : 'month' ,
523+ plan : {
524+ id : 'plan_trial' ,
525+ name : 'Pro' ,
526+ description : 'Pro plan' ,
527+ features : [ ] ,
528+ fee : {
529+ amount : 1000 ,
530+ amountFormatted : '10.00' ,
531+ currency : 'USD' ,
532+ currencySymbol : '$' ,
533+ } ,
534+ annualFee : {
535+ amount : 12000 ,
536+ amountFormatted : '120.00' ,
537+ currency : 'USD' ,
538+ currencySymbol : '$' ,
539+ } ,
540+ annualMonthlyFee : {
541+ amount : 1000 ,
542+ amountFormatted : '10.00' ,
543+ currency : 'USD' ,
544+ currencySymbol : '$' ,
545+ } ,
546+ slug : 'pro' ,
547+ avatarUrl : '' ,
548+ publiclyVisible : true ,
549+ isDefault : true ,
550+ isRecurring : true ,
551+ hasBaseFee : false ,
552+ forPayerType : 'user' ,
553+ freeTrialDays : 7 ,
554+ freeTrialEnabled : true ,
555+ } ,
556+ paymentSource : undefined ,
557+ confirm : jest . fn ( ) ,
558+ freeTrialEndsAt : new Date ( '2025-08-19' ) ,
559+ } as any ) ;
560+
561+ const { baseElement, getByText, getByRole, userEvent } = render (
562+ < Drawer . Root
563+ open
564+ onOpenChange = { ( ) => { } }
565+ >
566+ < Checkout
567+ planId = 'plan_with_payment_sources'
568+ planPeriod = 'month'
569+ />
570+ </ Drawer . Root > ,
571+ { wrapper } ,
572+ ) ;
573+
574+ await waitFor ( async ( ) => {
575+ // Verify checkout title is displayed
576+ expect ( getByRole ( 'heading' , { name : 'Checkout' } ) ) . toBeVisible ( ) ;
577+
578+ // Verify segmented control for payment method source is rendered
579+ const paymentMethodsButton = getByText ( 'Payment Methods' ) ;
580+ expect ( paymentMethodsButton ) . toBeVisible ( ) ;
581+
582+ const addPaymentMethodButton = getByText ( 'Add payment method' ) ;
583+ expect ( addPaymentMethodButton ) . toBeVisible ( ) ;
584+
585+ await userEvent . click ( paymentMethodsButton ) ;
586+ } ) ;
587+
588+ await waitFor ( ( ) => {
589+ const visaPaymentSource = getByText ( 'visa' ) ;
590+ expect ( visaPaymentSource ) . toBeVisible ( ) ;
591+
592+ const last4Digits = getByText ( '⋯ 4242' ) ;
593+ expect ( last4Digits ) . toBeVisible ( ) ;
594+
595+ // Verify the default badge is shown for the first payment source
596+ const defaultBadge = getByText ( 'Default' ) ;
597+ expect ( defaultBadge ) . toBeVisible ( ) ;
598+
599+ // Verify the hidden input contains the correct payment source id
600+ const hiddenInput = baseElement . querySelector ( 'input[name="payment_source_id"]' ) ;
601+ expect ( hiddenInput ) . toHaveAttribute ( 'value' , 'pm_test_visa' ) ;
602+ } ) ;
603+ } ) ;
468604} ) ;
0 commit comments