Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save bekarice/99d479e6c91880b3b80a21a0b3415b7f to your computer and use it in GitHub Desktop.
Save bekarice/99d479e6c91880b3b80a21a0b3415b7f to your computer and use it in GitHub Desktop.
Prevents checkout if the WooCommerce cart only contains items from a specific category
<?php // only copy this line if needed
/**
* Renders a notice and prevents checkout if the cart
* only contains products in a specific category
*/
function sv_wc_prevent_checkout_for_category() {
// set the slug of the category for which we disallow checkout
$category = 'clothing';
// get the product category
$product_cat = get_term_by( 'slug', $category, 'product_cat' );
// sanity check to prevent fatals if the term doesn't exist
if ( is_wp_error( $product_cat ) ) {
return;
}
$category_name = '<a href="' . get_term_link( $category, 'product_cat' ) . '">' . $product_cat->name . '</a>';
// check if this category is the only thing in the cart
if ( sv_wc_is_category_alone_in_cart( $category ) ) {
// render a notice to explain why checkout is blocked
wc_add_notice( sprintf( 'Hi there! Looks like your cart only contains products from the %1$s category &ndash; you must purchase a product from another category to check out.', $category_name ), 'error' );
}
}
add_action( 'woocommerce_check_cart_items', 'sv_wc_prevent_checkout_for_category' );
/**
* Checks if a cart contains exclusively products in a given category
*
* @param string $category the slug of the product category
* @return bool - true if the cart only contains the given category
*/
function sv_wc_is_category_alone_in_cart( $category ) {
// check each cart item for our category
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// if a product is not in our category, bail out since we know the category is not alone
if ( ! has_term( $category, 'product_cat', $cart_item['data']->id ) ) {
return false;
}
}
// if we're here, all items in the cart are in our category
return true;
}
@twigling
Copy link

This snippet works great, I just have one little issue with it... When the cart is empty, it also gives the warning message that I have set for when the snippet takes effect, and I would prefer that this was not the case. How can I fix this, please?

@indextwo
Copy link

indextwo commented May 28, 2017

@twigling If you wrap a conditional around everything in the sv_wc_is_category_alone_in_cart() function:

if (!WC()->cart->is_empty()) {
	// All the code
} else {
	return false;	//	Assume you'd want false here, since the cart is empty
}

I haven't tested this, but it should work as expected.

@twigling
Copy link

twigling commented May 29, 2017

Thank you for taking the time to respond :)!!

I feel mildly embarrassed to admit this, but I can't quite wrap my head around the concept of wrapping the conditional around the function, possibly because the sv_wc_is_category_alone_in_cart() comes up twice (but that is really just a part of my failure to comprehend, code is not my forté) could I ask you to please post an example of the entire code above, as it should be with the conditional in the correct place??

Pasting the snippet anywhere I believe it might fit, returns "Call to a member function is_empty() on null" for the line the code is in... I get that I am doing something incorrectly, but I am not getting enough information from the error message to be able to correct it.

ETA: After subconsciously dwelling on this for some hours, I think I might have had a lightbulb... will test my theory after dinner.

ETA2: Nope, whatever I did, it wasn't right.

To be more specific, when the cart is empty, it throws up both the usual "cart is empty" message AND the "you only have products from X category in your cart". I am making an uneducated guess in placing everything from line 38 down into the spot marked //all the code (also tried with the whole snippet in that spot), and get the same error message as before.

I also tried putting it in the second closed bracket after ->is empty and while it gives a different error, its clearly not right..

Thus, can someone please (pretty, with sugar on it) take a look and clarify where I should put what to make it work, or test the combination to see if it can indeed work as intended?

@indextwo
Copy link

Hey @twigling

Looking at my code, it probably makes more sense to bail within the action rather than sv_wc_is_category_alone_in_cart(). So try this instead:

function sv_wc_prevent_checkout_for_category() {
	//	If the cart is empty, then let's hit the ejector seat
	
	if (WC()->cart->is_empty()) {
		return;
	}		

	// set the slug of the category for which we disallow checkout
	$category = 'clothing';
	
	// get the product category
	$product_cat = get_term_by( 'slug', $category, 'product_cat' );
	
	// sanity check to prevent fatals if the term doesn't exist
	if ( is_wp_error( $product_cat ) ) {
		return;
	}
	
	$category_name = '<a href="' . get_term_link( $category, 'product_cat' ) . '">' . $product_cat->name . '</a>';
	
	// check if this category is the only thing in the cart
	if ( sv_wc_is_category_alone_in_cart( $category ) ) {
		
		// render a notice to explain why checkout is blocked
		wc_add_notice( sprintf( 'Hi there! Looks like your cart only contains products from the %1$s category &ndash; you must purchase a product from another category to check out.', $category_name ), 'error' );
	}
}

add_action( 'woocommerce_check_cart_items', 'sv_wc_prevent_checkout_for_category' );

That should behave the way you want: if the cart is empty it will stop trying to process anything else, so you should never get that message.

@twigling
Copy link

Thank you for your input. I have disabled the original snippet and set this one instead. I cannot tell you if it works, because now both cart and checkout load with everything blank below the page title... so I can't even empty the cart. For the sake of checking, I changed the theme of my site to twenty-fourteen but that didn't do anything...

@indextwo
Copy link

@twigling You should still use the original snippet, but replace the sv_wc_prevent_checkout_for_category with the one I gave you - there are function calls in there, so if you're removed those functions, it's likely to error out.

  1. Take the original snippet;
  2. Delete lines 7-29;
  3. Paste in the sv_wc_prevent_checkout_for_category with the add_action line from my previous comment

@twigling
Copy link

Yesssssssss, that works! Thank you so much for your help, patience and explanations 👯‍♂️ :D

@Pami72
Copy link

Pami72 commented Apr 4, 2018

Ok So I tried to use this in my cart and It didnt work... can someone Help me please....

@Pami72
Copy link

Pami72 commented Apr 4, 2018

can someone help me to get this to work... I am having issues
Thanks!

@vtsara
Copy link

vtsara commented May 22, 2018

Wanting to alter this a bit so that I can prevent checkout if the cart contains a certain combination of categories. So, I need to check to see if $category I've set AND any other category are present, if yes then prevent checkout. Or, only allow checkout if $category I set is either alone, or not present at all. The part that's tripping me up is looking for a specific category plus "any other category" ...any suggestions on this part?

Currently have this which is working almost as intended - works when catering category is alone or is combined w/ other items in cart, but still giving the error when non-catering items are alone in the cart:

// check if category is the only thing in the cart if ( lg_wc_is_category_alone_in_cart( $category ) ) { // if yes then escape this because it's all good return; } else { // otherwise post a notice to explain why checkout is blocked wc_add_notice( sprintf( 'hi there! looks like your cart contains products from the %1$s and <a href="#">pickup</a> categories &ndash; catering items must be ordered separately from pickup and shipping items to check out.', $category_name ), 'error' ); }

I feel like I'm close?

@Nodws
Copy link

Nodws commented Feb 21, 2019

This does not "prevent" checkout just escapes a notice, add exit(); to prevent lol

@theRealRizeo
Copy link

theRealRizeo commented Apr 10, 2019

To make sure the notice shows and sticks: (Updated code)

function sv_wc_prevent_checkout_for_category() {
	//	If the cart is empty, then let's hit the ejector seat
	
	if (WC()->cart->is_empty()) {
		return;
	}		

	// set the slug of the category for which we disallow checkout
	$category = 'clothing';
	
	// get the product category
	$product_cat = get_term_by( 'slug', $category, 'product_cat' );
	
	// sanity check to prevent fatals if the term doesn't exist
	if ( is_wp_error( $product_cat ) ) {
		return;
	}
	
	$category_name = '<a href="' . get_term_link( $category, 'product_cat' ) . '">' . $product_cat->name . '</a>';
	
	// check if this category is the only thing in the cart
	if ( sv_wc_is_category_alone_in_cart( $category ) ) {
		
		// render a notice to explain why checkout is blocked
		wc_add_notice( sprintf( 'Hi there! Looks like your cart only contains products from the %1$s category &ndash; you must purchase a product from another category to check out.', $category_name ), 'error' );
return false;
	}
return true;
}

add_action( 'woocommerce_check_cart_items', 'sv_wc_prevent_checkout_for_category' );

@theRealRizeo
Copy link

You need to return true or false. false tells WooCommerce there is an error and picks your notice

@rtpHarry
Copy link

rtpHarry commented Dec 9, 2019

Thanks for this!

For future readers: I was hunting around trying to figure this out and this thread helped but it seems that it's not the return false or true that blocks the checkout.

It's the wp_add_notice with an error parameter that does it:

		// Check cart contents for errors.
		do_action( 'woocommerce_check_cart_items' );
		// Calc totals.
		WC()->cart->calculate_totals();
		// Get checkout object.
		$checkout = WC()->checkout();
		if ( empty( $_POST ) && wc_notice_count( 'error' ) > 0 ) { // WPCS: input var ok, CSRF ok.
			wc_get_template( 'checkout/cart-errors.php', array( 'checkout' => $checkout ) );
			wc_clear_notices();
		} else {

You can see it doesn't do anything but called the woocommerce_check_cart_items action.

The bit that sends it to the blocked checkout page is finding wc_notice_count( 'error' ) > 0.

@rtpHarry
Copy link

rtpHarry commented Dec 9, 2019

Bonus tip: I wanted to inject my own product configuration page in as the checkout error page.

To this you can do two things:

  1. Use is_checkout() in your woocommerce_check_cart_items handler. This way you can add an error to block the checkout, but not actually display it anywhere.
  2. Override the template for the checkout error page to show your own configuration page.

Code would look something like this:

        /**
         * Configure the action and filter hooks
         * 
         * @since   1.0.0
         */
        public function plugin_init()
        {
            add_action('woocommerce_check_cart_items', array(&$this, 'woocommerce_check_cart_items_valid'));
            add_filter('wc_get_template', array(&$this, 'woocommerce_template_override'), 10, 5);
        }

        /**
         * Halt the checkout if cart contains unconfigured products
         * 
         * @since 1.0.0
         */
        public function woocommerce_check_cart_items_valid()
        {
            // Only run in the Checkout page for the reason explained below
            if (is_checkout()) {
                if (!$this->is_cart_valid()) {
                    // must add an error notice otherwise it won't block the checkout.
                    // this won't actually be seen anywhere as its only applied on the checkout
                    // page and checkout redirects to the cart errors page instead
                    wc_add_notice(
                        __('You must configure your products before you can checkout.', 'textdomain-here'),
                        'error'
                    );
                }
            }
        }

        /**
         * Override the checkout error page if the cart contains unconfigured products
         * 
         * @since 1.0.0
         */
        public function woocommerce_template_override($located, $template_name, $args, $template_path, $default_path)
        {
            if ('checkout/cart-errors.php' == $template_name) {
                if (!$this->is_cart_valid()) {
                    $located = MY_PLUGIN_DIR . 'templates/checkout/configure-products.php';
                }
            }

            return $located;
        }

@DamianKardas
Copy link

Hi, it's there a way to make this for two or more categories? so If customer have in cart something from Category A and from Category B then will see message and not allow to go to checkout? or if got Category B and Category C also, or Cat A + Cat B + Cat C?

@ArneSaknussemm
Copy link

Bonus tip: I wanted to inject my own product configuration page in as the checkout error page.

To this you can do two things:

1. Use `is_checkout()` in your `woocommerce_check_cart_items` handler. This way you can add an error to block the checkout, but not actually display it anywhere.

2. Override the template for the checkout error page to show your own configuration page.

Code would look something like this:

        /**
         * Configure the action and filter hooks
         * 
         * @since   1.0.0
         */
        public function plugin_init()
        {
            add_action('woocommerce_check_cart_items', array(&$this, 'woocommerce_check_cart_items_valid'));
            add_filter('wc_get_template', array(&$this, 'woocommerce_template_override'), 10, 5);
        }

        /**
         * Halt the checkout if cart contains unconfigured products
         * 
         * @since 1.0.0
         */
        public function woocommerce_check_cart_items_valid()
        {
            // Only run in the Checkout page for the reason explained below
            if (is_checkout()) {
                if (!$this->is_cart_valid()) {
                    // must add an error notice otherwise it won't block the checkout.
                    // this won't actually be seen anywhere as its only applied on the checkout
                    // page and checkout redirects to the cart errors page instead
                    wc_add_notice(
                        __('You must configure your products before you can checkout.', 'textdomain-here'),
                        'error'
                    );
                }
            }
        }

        /**
         * Override the checkout error page if the cart contains unconfigured products
         * 
         * @since 1.0.0
         */
        public function woocommerce_template_override($located, $template_name, $args, $template_path, $default_path)
        {
            if ('checkout/cart-errors.php' == $template_name) {
                if (!$this->is_cart_valid()) {
                    $located = MY_PLUGIN_DIR . 'templates/checkout/configure-products.php';
                }
            }

            return $located;
        }

Great, this cart checking is so much better than https://docs.woocommerce.com/document/minimum-order-amount/

@abdigital
Copy link

abdigital commented Nov 23, 2021

I'm trying to figure out how can we post a message and disable place order when a specific product from a list of 6 products IDs is not added to cart. Any ideas? Thank you

@nafanoul
Copy link

If i want to change the behavior at checkout as seen on line 22-27, how do I do that? I want to redirect to a different URL. I have tried replacing 22-27 with;
if ( sv_wc_is_category_alone_in_cart( $category ) ) {

	// render a notice to explain why checkout is blocked
	wp_redirect( 'example.com' );
		exit();
	
}

However it doesnt seem to work. Im new at this so any help would be appreciated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment