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);
@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