Repository: woocommerce/woocommerce
Pull request #57834: Allow saving cards if customer is creating an account
Author: login: senadir
Labels: name: Enhancement
description: The issue is a request for an enhancement.
name: plugin: woocommerce
description: Issues related to the WooCommerce Core plugin.
name: Rubik
description: Store API checkout endpoints, Mini-Cart, Cart and Checkout related issues
Created: 2025-05-07T14:39:13Z | Merged: 2025-05-09T11:16:54Z

### Changes proposed in this Pull Request:

This PR enables the "save card for future use" if the guest customer is also opting in to create an account, or is forced to create an account. Previously it would only show if you're logged in already, missing this for first time guest users creating an account.

Closes WOOPLUG-4220
### How to test the changes in this Pull Request:

<!-- Include detailed instructions on how these changes can be tested. Review and follow the guide for how to write high-quality testing instructions. -->

Using the [WooCommerce Testing Instructions Guide](https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions), include your detailed testing instructions:

1. Install a credit card based payment method like Stripe.
2. Enable guest checkout and creating account during Checkout.
3. In Checkout, guest, check the create account box.
4. Notice that save card checkbox is now visible, check it.
5. Place the order, add an item to cart, go to Checkout
6. Notice that the card is saved.
7. Disable guest checkout.
8. Go to Checkout incognito, notice that you can save the card.

<!-- End testing instructions -->

### Testing that has already taken place:

<!-- Detail any testing that has already been conducted. -->
<!-- Include environment details such as hosting type, plugins, theme, store size, store age, and relevant settings. -->
<!-- Mention any analysis performed, such as assessing potential impacts on environment attributes and other plugins, performance profiling, or LLM/AI-based analysis. -->
<!-- Within the testing details you provide, please ensure that no sensitive information (such as API keys, passwords, user data, etc.) is included in this public pull request. -->

### Changelog entry

<!-- You can optionally choose to enter a changelog entry by checking the box below and supplying data. -->
<!-- It will trigger the 'Add changelog to PR' CI job to create and push the entry into the branch. -->

<!-- Due to org permissions, the job might fail for PRs crated from a fork under GitHub organizations. Possible solutions: -->
<!-- * Create entry manually with `pnpm --filter='@woocommerce/plugin-woocommerce' changelog add` and push it into the branch (replace `@woocommerce/plugin-woocommerce` with package name from nearest `package.json` file) -->
<!-- * Create entry from supplied PR data and push it automatically `pnpm utils changefile pr-number-here -o github-org-name-here` -->

-   [x] Automatically create a changelog entry from the details below.

<!-- If no changelog entry is required for this PR, you can specify that below and provide a comment explaining why. This cannot be used if you selected the option to automatically create a changelog entry above. -->

-   [ ] This Pull Request does not require a changelog entry. (Comment required below)

<details>

<summary>Changelog Entry Details</summary>

#### Significance

<!-- Choose only one -->

-   [x] Patch
-   [ ] Minor
-   [ ] Major

#### Type

<!-- Choose only one -->

-   [ ] Fix - Fixes an existing bug
-   [ ] Add - Adds functionality
-   [ ] Update - Update existing functionality
-   [ ] Dev - Development related task
-   [ ] Tweak - A minor adjustment to the codebase
-   [ ] Performance - Address performance issues
-   [x] Enhancement - Improvement to existing functionality

#### Message <!-- Add a changelog message here -->

Allow saving cards if a guest customer is also creating an account.

</details>

<details>

<summary>Changelog Entry Comment</summary>

#### Comment <!-- If your Pull Request doesn't require a changelog entry, a comment explaining why is required instead -->

</details>



Diff:
diff --git a/plugins/woocommerce/changelog/57834-add-collect-card-if-registration-on b/plugins/woocommerce/changelog/57834-add-collect-card-if-registration-on
new file mode 100644
index 0000000000000..64fbbb55f211e
--- /dev/null
+++ b/plugins/woocommerce/changelog/57834-add-collect-card-if-registration-on
@@ -0,0 +1,4 @@
+Significance: patch
+Type: enhancement
+
+Allow saving cards if a guest customer is also creating an account.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/place-order-button/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/place-order-button/index.tsx
index 08a124bac9abe..168c9c6d8bff4 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/place-order-button/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/place-order-button/index.tsx
@@ -6,7 +6,8 @@ import {
 	useCheckoutSubmit,
 	useStoreCart,
 } from '@woocommerce/base-context/hooks';
-import { Icon, check } from '@wordpress/icons';
+import { check } from '@wordpress/icons';
+import { Icon } from '@wordpress/components';
 import Button from '@woocommerce/base-components/button';
 import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
 import {
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx
index 67d9aedcfa63d..65e13ad1d3b0a 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx
@@ -106,7 +106,7 @@ export const TotalsCoupon = ( {
 							className="wc-block-components-totals-coupon__input"
 							label={ __( 'Enter code', 'woocommerce' ) }
 							value={ couponValue }
-							ariaDescribedBy={ validationErrorId }
+							ariaDescribedBy={ validationErrorId || '' }
 							onChange={ ( newCouponValue ) => {
 								setCouponValue( newCouponValue );
 							} }
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx
index 4f4286a61cc92..7d3f910361cd2 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx
@@ -5,10 +5,12 @@ import { __ } from '@wordpress/i18n';
 import { useEditorContext } from '@woocommerce/base-context';
 import { CheckboxControl } from '@woocommerce/blocks-components';
 import { useSelect, useDispatch } from '@wordpress/data';
+import { useEffect } from '@wordpress/element';
 import {
 	checkoutStore as checkoutStoreDescriptor,
 	paymentStore,
 } from '@woocommerce/block-data';
+import { getSetting } from '@woocommerce/settings';
 
 /**
  * Internal dependencies
@@ -34,21 +36,46 @@ const PaymentMethodCard = ( {
 	showSaveOption,
 }: PaymentMethodCardProps ) => {
 	const { isEditor } = useEditorContext();
-	const { shouldSavePaymentMethod, customerId } = useSelect( ( select ) => {
-		const paymentMethodStore = select( paymentStore );
-		const checkoutStore = select( checkoutStoreDescriptor );
-		return {
-			shouldSavePaymentMethod:
-				paymentMethodStore.getShouldSavePaymentMethod(),
-			customerId: checkoutStore.getCustomerId(),
-		};
-	} );
+	const { shouldSavePaymentMethod, customerId, shouldCreateAccount } =
+		useSelect( ( select ) => {
+			const paymentMethodStore = select( paymentStore );
+			const checkoutStore = select( checkoutStoreDescriptor );
+			return {
+				shouldSavePaymentMethod:
+					paymentMethodStore.getShouldSavePaymentMethod(),
+				customerId: checkoutStore.getCustomerId(),
+				shouldCreateAccount: checkoutStore.getShouldCreateAccount(),
+			};
+		}, [] );
+
 	const { __internalSetShouldSavePaymentMethod } =
 		useDispatch( paymentStore );
+
+	const allowGuestCheckout = getSetting( 'checkoutAllowsGuest', false );
+
+	// Work out if the customer can save the payment method.
+	const canSavePaymentMethod =
+		// They're already logged in.
+		customerId > 0 ||
+		// They're not logged in, but they're creating an account.
+		shouldCreateAccount ||
+		// They're not logged in, but they must create an account.
+		! allowGuestCheckout;
+
+	useEffect( () => {
+		if ( ! canSavePaymentMethod && shouldSavePaymentMethod ) {
+			__internalSetShouldSavePaymentMethod( false );
+		}
+	}, [
+		canSavePaymentMethod,
+		shouldSavePaymentMethod,
+		__internalSetShouldSavePaymentMethod,
+	] );
+
 	return (
 		<PaymentMethodErrorBoundary isEditor={ isEditor }>
 			{ children }
-			{ customerId > 0 && showSaveOption && (
+			{ canSavePaymentMethod && showSaveOption && (
 				<CheckboxControl
 					className="wc-block-components-payment-methods__save-card-info"
 					label={ __(
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-cart-items/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-cart-items/edit.tsx
index d5372dc04bd33..f1a18716f480b 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-cart-items/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-cart-items/edit.tsx
@@ -12,8 +12,8 @@ import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
 import Block from './block';
 
 export type BlockAttributes = {
-	className: string;
-	disableProductDescriptions: boolean;
+	className?: string;
+	disableProductDescriptions?: boolean;
 };
 
 export const Edit = ( {
@@ -23,7 +23,7 @@ export const Edit = ( {
 	attributes: BlockAttributes;
 	setAttributes: ( attributes: Record< string, unknown > ) => void;
 } ): JSX.Element => {
-	const { className, disableProductDescriptions } = attributes;
+	const { className = '', disableProductDescriptions = false } = attributes;
 	const blockProps = useBlockProps();
 
 	return (
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
index c3d3cce1118ae..d5e56f36d6158 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
@@ -4,9 +4,10 @@
 import { render, screen, waitFor, act } from '@testing-library/react';
 import { previewCart } from '@woocommerce/resource-previews';
 import { dispatch } from '@wordpress/data';
-import { CART_STORE_KEY, checkoutStore } from '@woocommerce/block-data';
+import { cartStore, checkoutStore } from '@woocommerce/block-data';
 import { default as fetchMock } from 'jest-fetch-mock';
 import { allSettings } from '@woocommerce/settings';
+import { registerPaymentMethod } from '@woocommerce/blocks-registry';
 
 /**
  * Internal dependencies
@@ -90,7 +91,9 @@ jest.mock( '../context', () => {
 	};
 } );
 
-import { useCheckoutBlockContext } from '../context';
+/** @type {jest.Mock} */
+const useCheckoutBlockContext =
+	jest.requireMock( '../context' ).useCheckoutBlockContext;
 
 const CheckoutBlock = () => {
 	return (
@@ -138,8 +141,8 @@ describe( 'Testing Checkout', () => {
 				return Promise.resolve( '' );
 			} );
 			// need to clear the store resolution state between tests.
-			dispatch( CART_STORE_KEY ).invalidateResolutionForStore();
-			dispatch( CART_STORE_KEY ).receiveCart( defaultCartState.cartData );
+			dispatch( cartStore ).invalidateResolutionForStore();
+			dispatch( cartStore ).receiveCart( defaultCartState.cartData );
 		} );
 	} );
 
@@ -157,6 +160,97 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		act( () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		expect(
+			await screen.findByText( /Payment method with cards/i )
+		).toBeInTheDocument();
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		act( () => {
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		act( () => {
+			allSettings.checkoutAllowsGuest = true;
+			allSettings.checkoutAllowsSignup = true;
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( true );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		act( () => {
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( false );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		act( () => {
+			allSettings.checkoutAllowsGuest = false;
+			allSettings.checkoutAllowsSignup = true;
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		// cleanup
+		act( () => {
+			allSettings.checkoutAllowsGuest = undefined;
+			allSettings.checkoutAllowsSignup = undefined;
+			dispatch( checkoutStore ).__internalSetCustomerId( 1 );
+		} );
+	} );
+
 	it( 'Renders the shipping address card if the address is filled and the cart contains a shippable product', async () => {
 		act( () => {
 			const cartWithAddress = {
@@ -210,7 +304,7 @@ describe( 'Testing Checkout', () => {
 
 		// Async is needed here despite the IDE warnings. Testing Library gives a warning if not awaited.
 		await act( () =>
-			dispatch( CART_STORE_KEY ).setShippingAddress( {
+			dispatch( cartStore ).setShippingAddress( {
 				first_name: 'First Name JP',
 				last_name: 'Last Name JP',
 				company: '',
@@ -233,7 +327,7 @@ describe( 'Testing Checkout', () => {
 
 		// Testing the default address format
 		await act( () =>
-			dispatch( CART_STORE_KEY ).setShippingAddress( {
+			dispatch( cartStore ).setShippingAddress( {
 				first_name: 'First Name GB',
 				last_name: 'Last Name GB',
 				company: '',


Reviews:
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T13:40:28Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T13:41:43Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T13:42:12Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:37:02Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:39:14Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:39:30Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:39:41Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:39:53Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:40:04Z
user: login: straku
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:40:16Z
user: login: senadir
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T09:47:01Z
user: login: senadir
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T09:48:02Z
user: login: straku
body: Thank you for applying the changes! I think in your recent edits, you've introduced some errors that should be easy to fix locally (lint + undefined variable). Leaving approve for you to fix them and land it at your own leisure. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T10:34:48Z

Review comments:
diff_hunk: @@ -34,21 +36,42 @@ const PaymentMethodCard = ( {
 	showSaveOption,
 }: PaymentMethodCardProps ) => {
 	const { isEditor } = useEditorContext();
-	const { shouldSavePaymentMethod, customerId } = useSelect( ( select ) => {
-		const paymentMethodStore = select( paymentStore );
-		const checkoutStore = select( checkoutStoreDescriptor );
-		return {
-			shouldSavePaymentMethod:
-				paymentMethodStore.getShouldSavePaymentMethod(),
-			customerId: checkoutStore.getCustomerId(),
-		};
-	} );
+	const { shouldSavePaymentMethod, customerId, createAccount } = useSelect(
path: plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx
user: login: straku
body: `useSelect` is typed to require two arguments, I know it works properly without providing a second argument, but the return values are all typed as `any` when used like that. If you provide second argument as `[]` the return values are properly typed:
```
{
  shouldSavePaymentMethod: boolean;
  customerId: number;
  createAccount: boolean;
}
```
After looking at the type of `createAccount`, it would be clearer to rename it to `shouldCreateAccount`. What do you think?
created_at: 2025-05-08T13:40:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,21 +36,42 @@ const PaymentMethodCard = ( {
 	showSaveOption,
 }: PaymentMethodCardProps ) => {
 	const { isEditor } = useEditorContext();
-	const { shouldSavePaymentMethod, customerId } = useSelect( ( select ) => {
-		const paymentMethodStore = select( paymentStore );
-		const checkoutStore = select( checkoutStoreDescriptor );
-		return {
-			shouldSavePaymentMethod:
-				paymentMethodStore.getShouldSavePaymentMethod(),
-			customerId: checkoutStore.getCustomerId(),
-		};
-	} );
+	const { shouldSavePaymentMethod, customerId, createAccount } = useSelect(
+		( select ) => {
+			const paymentMethodStore = select( paymentStore );
+			const checkoutStore = select( checkoutStoreDescriptor );
+			return {
+				shouldSavePaymentMethod:
+					paymentMethodStore.getShouldSavePaymentMethod(),
+				customerId: checkoutStore.getCustomerId(),
+				createAccount: checkoutStore.getShouldCreateAccount(),
+			};
+		}
+	);
+
+	const allowGuestCheckout = getSetting( 'checkoutAllowsGuest', false );
+
+	// Work out if the customer can save the payment method.
+	const canSavePaymentMethod =
+		// They're already logged in.
+		customerId > 0 ||
+		// They're not logged in, but they're creating an account.
+		createAccount ||
+		// They're not logged in, but they must create an account.
+		! allowGuestCheckout;
+
+	useEffect( () => {
+		if ( ! canSavePaymentMethod && shouldSavePaymentMethod ) {
+			__internalSetShouldSavePaymentMethod( false );
path: plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx
user: login: straku
body: There's a lint error here, you need to move `useDispatch` usage below above it to get rid of it.
created_at: 2025-05-08T13:41:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,21 +36,42 @@ const PaymentMethodCard = ( {
 	showSaveOption,
 }: PaymentMethodCardProps ) => {
 	const { isEditor } = useEditorContext();
-	const { shouldSavePaymentMethod, customerId } = useSelect( ( select ) => {
-		const paymentMethodStore = select( paymentStore );
-		const checkoutStore = select( checkoutStoreDescriptor );
-		return {
-			shouldSavePaymentMethod:
-				paymentMethodStore.getShouldSavePaymentMethod(),
-			customerId: checkoutStore.getCustomerId(),
-		};
-	} );
+	const { shouldSavePaymentMethod, customerId, createAccount } = useSelect(
+		( select ) => {
+			const paymentMethodStore = select( paymentStore );
+			const checkoutStore = select( checkoutStoreDescriptor );
+			return {
+				shouldSavePaymentMethod:
+					paymentMethodStore.getShouldSavePaymentMethod(),
+				customerId: checkoutStore.getCustomerId(),
+				createAccount: checkoutStore.getShouldCreateAccount(),
+			};
+		}
+	);
+
+	const allowGuestCheckout = getSetting( 'checkoutAllowsGuest', false );
+
+	// Work out if the customer can save the payment method.
+	const canSavePaymentMethod =
+		// They're already logged in.
+		customerId > 0 ||
+		// They're not logged in, but they're creating an account.
+		createAccount ||
+		// They're not logged in, but they must create an account.
+		! allowGuestCheckout;
+
+	useEffect( () => {
+		if ( ! canSavePaymentMethod && shouldSavePaymentMethod ) {
+			__internalSetShouldSavePaymentMethod( false );
+		}
+	}, [ shouldSavePaymentMethod, canSavePaymentMethod ] );
path: plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-card.tsx
user: login: straku
body: ```suggestion
	}, [
		shouldSavePaymentMethod,
		canSavePaymentMethod,
		__internalSetShouldSavePaymentMethod,
	] );
```
created_at: 2025-05-08T13:42:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
+		expect(
+			screen.getByText( /Payment method with cards/i )
+		).toBeInTheDocument();
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: ```suggestion
		expect(
			await screen.findByText( /Payment method with cards/i )
		).toBeInTheDocument();
```
This way, you avoid checking the implementation details of the component and wait for what the customer expects. Not feeling strongly about it though, there are cases where waiting for the fetch to finish is much simpler. Proceed as you wish 😊
created_at: 2025-05-09T09:37:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: I don't think this needs to be wrapped with `act`. I tried locally and the test is passing, and I am not getting any errors.
created_at: 2025-05-09T09:39:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
+		expect(
+			screen.getByText( /Payment method with cards/i )
+		).toBeInTheDocument();
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: ```suggestion
		act( () => {
```
created_at: 2025-05-09T09:39:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
+		expect(
+			screen.getByText( /Payment method with cards/i )
+		).toBeInTheDocument();
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: ```suggestion
		act( () => {
```
created_at: 2025-05-09T09:39:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
+		expect(
+			screen.getByText( /Payment method with cards/i )
+		).toBeInTheDocument();
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		await act( async () => {
+			allSettings.checkoutAllowsGuest = true;
+			allSettings.checkoutAllowsSignup = true;
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( true );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: ```suggestion
		act( () => {
```
created_at: 2025-05-09T09:39:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
+		expect(
+			screen.getByText( /Payment method with cards/i )
+		).toBeInTheDocument();
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		await act( async () => {
+			allSettings.checkoutAllowsGuest = true;
+			allSettings.checkoutAllowsSignup = true;
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( true );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( false );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: ```suggestion
		act( () => {
```
created_at: 2025-05-09T09:40:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
+			const PaymentMethodContent = () => <div>A payment method</div>;
+			registerPaymentMethod( {
+				name: 'test-payment-method',
+				label: 'Payment method with cards',
+				content: <PaymentMethodContent />,
+				edit: <PaymentMethodContent />,
+				icons: null,
+				canMakePayment: () => true,
+				supports: {
+					showSavedCards: true,
+					showSaveOption: true,
+					features: [ 'products' ],
+				},
+				ariaLabel: 'Test Payment Method',
+			} );
+		} );
+
+		const { rerender } = render( <CheckoutBlock /> );
+
+		await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
+		expect(
+			screen.getByText( /Payment method with cards/i )
+		).toBeInTheDocument();
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		await act( async () => {
+			allSettings.checkoutAllowsGuest = true;
+			allSettings.checkoutAllowsSignup = true;
+			dispatch( checkoutStore ).__internalSetCustomerId( 0 );
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( true );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		await act( async () => {
+			dispatch( checkoutStore ).__internalSetShouldCreateAccount( false );
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.queryByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).not.toBeInTheDocument();
+
+		await act( async () => {
+			allSettings.checkoutAllowsGuest = false;
+			allSettings.checkoutAllowsSignup = true;
+		} );
+
+		rerender( <CheckoutBlock /> );
+
+		expect(
+			screen.getByRole( 'checkbox', {
+				name: 'Save payment information to my account for future purchases.',
+			} )
+		).toBeInTheDocument();
+
+		// cleanup
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: straku
body: ```suggestion
		act( () => {
```
created_at: 2025-05-09T09:40:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,6 +164,98 @@ describe( 'Testing Checkout', () => {
 		expect( fetchMock ).toHaveBeenCalledTimes( 1 );
 	} );
 
+	it( 'Allows saving payment method if the customer is creating an account or has already logged in', async () => {
+		await act( async () => {
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: senadir
body: ```suggestion
	act( () => {
```
created_at: 2025-05-09T09:47:01Z
author_association: MEMBER
diff_hunk: @@ -4,9 +4,14 @@
 import { render, screen, waitFor, act } from '@testing-library/react';
 import { previewCart } from '@woocommerce/resource-previews';
 import { dispatch } from '@wordpress/data';
-import { CART_STORE_KEY, checkoutStore } from '@woocommerce/block-data';
+import {
+	cartStore,
+	checkoutStore,
+	paymentStore,
+} from '@woocommerce/block-data';
path: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/test/block.js
user: login: senadir
body: ```suggestion
import { CART_STORE_KEY, checkoutStore } from '@woocommerce/block-data';
```
created_at: 2025-05-09T09:48:02Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-07T14:39:40Z
body: ## Testing Guidelines
Hi @straku ,

Apart from reviewing the code changes, please make sure to **review the testing instructions** ([Guide](https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions)) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed.

**Reminder:** PR reviewers are required to document testing performed. This includes:
- 🖼️ **Screenshots or screen recordings**.
- 📝 **List of functionality tested / steps followed**.
- 🌐 **Site details** (environment attributes such as hosting type, plugins, theme, store size, store age, and relevant settings).
- 🔍 **Any analysis performed**, such as assessing potential impacts on environment attributes and other plugins, conducting performance profiling, or using LLM/AI-based analysis.

⚠️ Within the testing details you provide, please ensure that no sensitive information (such as API keys, passwords, user data, etc.) is included in this public issue.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-07T14:43:23Z
body: **Size Change:** +312 B (+0.01%) 

**Total Size:** 5.95 MB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./plugins/woocommerce/client/admin/build/chunks/settings-payments-main.js` | 23 kB | +148 B (+0.65%) |

<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-07T14:53:51Z
body: 
## Test using WordPress Playground
The changes in this pull request can be previewed and tested using a [WordPress Playground](https://developer.wordpress.org/playground/) instance.
[WordPress Playground](https://developer.wordpress.org/playground/) is an experimental project that creates a full WordPress instance entirely within the browser.

[Test this pull request with WordPress Playground](https://wordpress-playground.atomicsites.blog/#{"landingPage":"/wp-admin/admin.php?page=wc-admin","preferredVersions":{"php":"8.0","wp":"latest"},"phpExtensionBundles":["kitchen-sink"],"features":{"networking":true},"steps":[{"step":"installPlugin","pluginZipFile":{"resource":"url","url":"https://playground.wordpress.net/plugin-proxy.php?org=woocommerce&repo=woocommerce&workflow=Build%20Live%20Branch&artifact=plugins-14927063278&pr=57834"},"options":{"activate":true}},{"step":"installPlugin","pluginZipFile":{"resource":"url","url":"https://github-proxy.com/https://github.com/woocommerce/woocommerce/releases/download/wc-beta-tester-2.3.1/woocommerce-beta-tester.zip"},"options":{"activate":true}},{"step":"setSiteOptions","options":{"woocommerce_onboarding_profile":{"skipped":true}}},{"step":"login","username":"admin","password":"password"}],"plugins":[]}).

Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a new commit.

author_association: CONTRIBUTOR
user: login: straku
created_at: 2025-05-08T13:44:17Z
body: Works as expected in my testing 👏 I left some comments that, I think, should be addressed. It also feels like a nice target for a unit tests coverage, so adding few would be great.
author_association: CONTRIBUTOR
user: login: senadir
created_at: 2025-05-08T16:08:15Z
body: Thank you @straku I added integration tests, fixed some stuff around, and addressed feedback
author_association: MEMBER