Commit 376e1586c38 for woocommerce
commit 376e1586c38dd3daf90e535c157a4303efb2520d
Author: Neil Carlo Sucuangco <necafasu@gmail.com>
Date: Tue Mar 3 02:52:24 2026 +0800
fix: Validate sold individually products in shortcode cart/checkout (#62293)
* fix: Validate sold individually products in shortcode cart/checkout
* Add changefile(s) from automation for the following project(s): woocommerce
* Update class-wc-cart-test.php
* Update class-wc-cart.php
* fix: Validate sold individually products in shortcode cart/checkout
* Update class-wc-cart-test.php
* Add changefile(s) from automation for the following project(s): woocommerce
* Update class-wc-cart.php
* Lint fixes
* Update class-wc-cart-test.php
* Update class-wc-cart.php
* Lint fix
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation b/plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation
new file mode 100644
index 00000000000..a459a838abb
--- /dev/null
+++ b/plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fixed sold individually validation on shortcode cart and checkout pages. Products marked as "Sold Individually" after being added to cart now have quantity automatically reduced to 1 with correct totals calculation.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/class-wc-cart.php b/plugins/woocommerce/includes/class-wc-cart.php
index 5610d391290..7df4a6f4d07 100644
--- a/plugins/woocommerce/includes/class-wc-cart.php
+++ b/plugins/woocommerce/includes/class-wc-cart.php
@@ -774,6 +774,15 @@ class WC_Cart extends WC_Legacy_Cart {
$return = false;
}
+ $result = $this->check_cart_item_sold_individually();
+
+ if ( is_wp_error( $result ) ) {
+ foreach ( $result->get_error_messages() as $message ) {
+ wc_add_notice( $message, 'error' );
+ }
+ $return = false;
+ }
+
$result = $this->check_cart_item_stock();
if ( is_wp_error( $result ) ) {
@@ -820,6 +829,41 @@ class WC_Cart extends WC_Legacy_Cart {
return $return;
}
+ /**
+ * Looks through cart items and ensures sold individually products have quantity of 1.
+ *
+ * @since 10.7.0
+ * @return bool|WP_Error
+ */
+ public function check_cart_item_sold_individually() {
+ $errors = new WP_Error();
+
+ foreach ( $this->get_cart() as $cart_item_key => $values ) {
+ $product = $values['data'];
+
+ if ( ! $product || ! $product->exists() ) {
+ continue;
+ }
+
+ $product_id = $values['variation_id'] ? $values['variation_id'] : $values['product_id'];
+ $product_to_check = wc_get_product( $product_id );
+
+ if ( ! $product_to_check || ! $product_to_check->exists() ) {
+ continue;
+ }
+
+ if ( $product_to_check->is_sold_individually() && $values['quantity'] > 1 ) {
+ // Re-fetch and overwrite to reflect product changes made after item was added to cart.
+ $this->cart_contents[ $cart_item_key ]['data'] = $product_to_check;
+ $this->set_quantity( $cart_item_key, 1, false );
+ /* translators: %s: product name */
+ $errors->add( 'sold-individually', sprintf( __( 'You can only have 1 %s in your cart.', 'woocommerce' ), $product_to_check->get_name() ) );
+ }
+ }
+
+ return $errors->has_errors() ? $errors : true;
+ }
+
/**
* Looks through the cart to check each item is in stock. If not, add an error.
*
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php b/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
index 3a23a7cd40e..635fdb1bba4 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
@@ -112,6 +112,71 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
wp_delete_user( $user_id );
}
+ /**
+ * @testdox check_cart_items should reduce quantity to 1 when product is marked as sold individually after being added to cart
+ */
+ public function test_check_cart_items_reduces_sold_individually_quantity() {
+ WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
+
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_regular_price( 10 );
+ $product->save();
+
+ WC()->cart->add_to_cart( $product->get_id(), 2 );
+
+ $product->set_sold_individually( true );
+ $product->save();
+
+ WC()->session->set( 'wc_notices', null );
+
+ $result = WC()->cart->check_cart_items();
+ $this->assertFalse( $result, 'check_cart_items should return false when fixing sold individually quantity (indicating an issue was found)' );
+
+ $cart_contents_after = WC()->cart->get_cart();
+ $cart_item_after = array_values( $cart_contents_after )[0];
+ $this->assertEquals( 1, $cart_item_after['quantity'], 'Cart item quantity should be reduced to 1' );
+
+ $error_notices = wp_list_pluck( wc_get_notices( 'error' ), 'notice' );
+ $this->assertContains(
+ sprintf( 'You can only have 1 %s in your cart.', $product->get_name() ),
+ $error_notices
+ );
+
+ WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
+ $product->delete( true );
+ }
+
+ /**
+ * @testdox Sold individually product with quantity 1 should not trigger an error or get modified by check_cart_items
+ */
+ public function test_check_cart_items_does_not_modify_sold_individually_quantity_one() {
+ WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
+
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_regular_price( 10 );
+ $product->set_sold_individually( true );
+ $product->save();
+
+ WC()->cart->add_to_cart( $product->get_id(), 1 );
+
+ $result = WC()->cart->check_cart_items();
+ $this->assertTrue( $result, 'check_cart_items should return true when no issues found' );
+
+ $cart_contents = WC()->cart->get_cart();
+ $cart_item = array_values( $cart_contents )[0];
+ $this->assertEquals( 1, $cart_item['quantity'], 'Quantity should remain 1' );
+
+ $error_notices = wp_list_pluck( wc_get_notices( 'error' ), 'notice' );
+ $this->assertEmpty( $error_notices, 'No error notices should be added' );
+
+ WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
+ $product->delete( true );
+ }
+
/**
* @testdox should throw a notice to the cart if an "any" attribute is empty.
*/