Skip to content

Instantly share code, notes, and snippets.

@kane-c
Created July 23, 2014 05:40
Show Gist options
  • Save kane-c/5e4d53601564ed667fb9 to your computer and use it in GitHub Desktop.
Save kane-c/5e4d53601564ed667fb9 to your computer and use it in GitHub Desktop.
WordPress WooCommerce: Force local pickup as the only shipping option when products in the cart require it.
<?php
// Force pickup as a shipping option if one or more products in the catalog is marked as pickup only.
// To do this, add a shipping class with the slug 'pickup-only' then set products with that class as required.
// Add this script to your theme's functions.php or similar.
function hideShippingWhenPickupRequired($rates, $package)
{
foreach ($package['contents'] as $item) {
$product = $item['data'];
$shippingClass = $product->get_shipping_class();
if ('pickup-only' === $shippingClass) {
// The cart requires pickup
return array(
'local_pickup' => $rates['local_pickup'],
);
}
}
return $rates;
}
add_filter('woocommerce_package_rates', 'hideShippingWhenPickupRequired', 10, 2);
@rbrenton
Copy link

rbrenton commented Dec 4, 2018

Works with WooCommerce v3.4.5 with a shipping plugin where local_pickup has been modified to legacy_local_pickup.

function hide_shipping_on_local_pickup_required( $rates, $package )
{
    $local = [];

    foreach( $package['contents'] as $item )
    {
        $product = $item['data'];
        $shipping_class = $product->get_shipping_class();

        if( $shipping_class == 'local-pickup' )
        {
            foreach( $rates as $rate_id => $rate )
            {
                if( in_array( $rate->method_id, array( 'local_pickup', 'legacy_local_pickup' ) ) )
                {
                    $local[ $rate_id ] = $rate;
                    break;
                }
            }
        }
    }

    return !empty( $local ) ? $local : $rates;
}

add_filter( 'woocommerce_package_rates', 'hide_shipping_on_local_pickup_required', 10, 2 );

@PierreNodles
Copy link

Hi there,

Couldn't make it work on Wordpress 4.9.9, Woocommerce 3.5.4 but here's what's been working out just fine for me :

function my_hide_shipping_when_local_is_available( $rates ) {
  
  $local= array();
  
  foreach ( $rates as $rate_id => $rate ) {
    if ('local_pickup' === $rate->method_id ) {
      $local[ $rate_id ] = $rate;
    }
  }
  
  return !empty( $local ) ? $local : $rates;
}

add_filter( 'woocommerce_package_rates', 'my_hide_shipping_when_local_is_available', 100 );

Furthermore, I wanted to force the local pickup on my customer only when products with a specific shipping class were on the the cart.
Here's the final result :

// Replace 'shipping-class' with the specific shipping class you want to force the local pick up upon
// To make it more readable, replace $shippingClass with your class name, eg : $hugeProducts

function my_hide_shipping_when_local_is_available( $rates ) {
    $cart_items = WC()->cart->get_cart();
    $shippingClass = false;
    
    foreach ( $cart_items as $cart_item ) {
		$product = $cart_item['data'];
		$class   = $product->get_shipping_class();
		
		if ( 'shipping-class' == $class ) {
			$shippingClass = true;
		}
	}
    
	$local = array();
	foreach ( $rates as $rate_id => $rate ) {
		if ('local_pickup' === $rate->method_id ) {
            $local[ $rate_id ] = $rate;
        }
	}
    
    if ( !empty($local) && ($shippingClass == true) ) {
        return $local;
    } else {
        return $rates;
    }

}

add_filter( 'woocommerce_package_rates', 'my_hide_shipping_when_local_is_available', 100 );

@ludekcerny
Copy link

ludekcerny commented Nov 13, 2020

Hi

Snippets above doesn't work for me, but I found this working solution: WooCommerce: Hide Shipping Rate if Shipping Class @ Cart

Clear transients after implementation!


/**
 * @snippet       Disable Free Shipping if Cart has Shipping Class (WooCommerce 2.6+)
 * @how-to        Get CustomizeWoo.com FREE
 * @author        Rodolfo Melogli
 * @testedwith    WooCommerce 4.0
 * @donate $9     https://businessbloomer.com/bloomer-armada/
 */
  
add_filter( 'woocommerce_package_rates', 'businessbloomer_hide_free_shipping_for_shipping_class', 10, 2 );
   
function businessbloomer_hide_free_shipping_for_shipping_class( $rates, $package ) {
   $shipping_class_target = 59; // shipping class ID (to find it, see screenshot here: https://www.businessbloomer.com/woocommerce-disable-free-shipping-if-cart-has-shipping-class/ )
   $in_cart = false;
   foreach ( WC()->cart->get_cart_contents() as $key => $values ) {
      if ( $values[ 'data' ]->get_shipping_class_id() == $shipping_class_target ) {
         $in_cart = true;
         break;
      } 
   }
   if ( $in_cart ) {
      unset( $rates['flat_rate:1'] ); // shipping method with ID (to find it, see screenshot here: https://www.businessbloomer.com/woocommerce-disable-free-shipping-if-cart-has-shipping-class/)
   }

   return $rates;
}

@ssillen
Copy link

ssillen commented Apr 22, 2021

I expanded the above snippets with an extra shipping class for products that are not delivered separately.
Tested with Wordpress 5.7.1 and WooCommerce 5.2.2


/**
 * Hide all other methods if:
 * - At least one item has shipping class 'local-pickup-only'
 *   OR
 * - All items have shipping class 'no-individual-delivery'
**/

function check_local_pickup_only( $rates, $package ) {
    $localPickupOnly = false;
	$noIndividualDelivery = false;
	$regularDelivery = false;
    
    foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
		$product = $cart_item['data'];
		$class   = $product->get_shipping_class();
		
		switch ($class) {
			case 'local-pickup-only':
				$localPickupOnly = true;
				break 2;
			case 'no-individual-delivery':
				$noIndividualDelivery = true;
				break;
			default:
				$regularDelivery = true;
				break;
		}
	}
	if ( $noIndividualDelivery && !$regularDelivery ) {$localPickupOnly = true;}
    
	$local = array();
	foreach ( $rates as $rate_id => $rate ) {
		if ('local_pickup' === $rate->method_id ) {
            $local[ $rate_id ] = $rate;
        }
	}
    
    if ( !empty($local) && ($localPickupOnly == true) ) {
        return $local;
    } else {
        return $rates;
    }

}

add_filter( 'woocommerce_package_rates', 'check_local_pickup_only', 10, 2 );

@roboes
Copy link

roboes commented Apr 15, 2024

Here's my solution: for carts with one or more products belonging to the shipping class "Local Pickup Only" ($shipping_class_name = 'local-pickup-only'), only the local pickup location(s) (from the Local Pickup in Blocks) will appear.

Tested with WordPress 6.5.2 and WooCommerce 8.7.0.

Important: "Clear customer sessions" after implementation (WooCommerce > Status > Tools > Clear customer sessions).

// WooCommerce display only "Local pickup" location(s) (from new "WooCommerce Blocks: Local Pickup" - Settings > Shipment > "Local Pickup" tab - https://woocommerce.com/document/woocommerce-blocks-local-pickup/) if one or more products added to the cart belong to the $shipping_class_name = 'local-pickup-only', dynamically unsets all shipping methods except those with values starting with "pickup_location:".
// Please note that the new "WooCommerce Blocks: Local Pickup" does not work with Elementor as it requires the Checkout block to enable it (as described here https://woocommerce.com/document/using-the-new-block-based-checkout/).

add_filter($hook_name='woocommerce_package_rates', $callback='woocommerce_shipping_method_local_pickup_only', $priority=10, $accepted_args=1);

function woocommerce_shipping_method_local_pickup_only( $rates ) {
   if ( WC() ) {

	   // Setup
	   $shipping_class_name = 'local-pickup-only';

	   $in_cart = false;

	   foreach ( WC()->cart->get_cart_contents() as $key => $values ) {
		  if ( $values['data']->get_shipping_class() === $shipping_class_name ) {
			 $in_cart = true;
			 break;
		  }
	   }

	   if ( $in_cart ) {
		  // Unset all shipping methods except for the ones with value starting with "pickup_location:"
		  foreach ( $rates as $rate_key => $rate ) {
			 if ( strpos( $rate_key, 'pickup_location:' ) !== 0 ) {
				unset( $rates[ $rate_key ] );
			 }
		  }
	   }

	   return $rates;

   }
}

For those using the "legacy" local pickup, the code below accomplishes the same goal.

// WooCommerce display only chosen $local_pickup_ids if one or more products added to the cart belong to the $shipping_class_name = 'local-pickup-only', dynamically unsets all shipping methods except for chosen $local_pickup_ids.

add_filter($hook_name='woocommerce_package_rates', $callback='woocommerce_shipping_method_local_pickup_only', $priority=10, $accepted_args=1);

function woocommerce_shipping_method_local_pickup_only( $rates ) {
   if ( WC() ) {

	   // Setup
	   $shipping_class_name = 'local-pickup-only';
	   $local_pickup_ids = array('local_pickup:38');

	   $in_cart = false;

	   foreach ( WC()->cart->get_cart_contents() as $key => $values ) {
		  if ( $values['data']->get_shipping_class() === $shipping_class_name ) {
			 $in_cart = true;
			 break;
		  }
	   }

	   if ( $in_cart ) {
		   // Unset all shipping methods except for $local_pickup_ids
		   foreach ( $rates as $rate_key => $rate ) {
			   if ( !in_array($rate_key, $local_pickup_ids) ) {
				   unset( $rates[$rate_key] );
			   }
			}
		}

	   return $rates;

   }
}

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