1
votes

An online shop im working at uses 2 shipping methods: size based rates and local pickup.

The size based rates is the default shipping method, local pickup is only used when a customer clicks Click and pickup rather then Add to cart (internally, the product is added to the cart in both cases, but when using Click and pickup, extra cart item data is added to indicate this).

Calculating the total shipping cost when all cart items are reserved with Click and pickup is no problem, since you can filter the shipping rates at runtime with the filter woocommerce_package_rates and change the shipping method so it will affect all cart items.

However, if the cart consists of items added with both Click and pickup and add to cart, it becomes more difficult to calculate the total shipping cost, because the shipping methods must be changed per cart item, and not affect all cart items.

Desired flow:

Cart items: 3
Book x 2 reserved with Click and pickup (cost: 0, local pickup shipping method)
Paper x 1 added to cart the normal way, (cost: default size rates, default shipping method)
Total shipping cost: local pickup cost + default shipping cost

Current code:

function cust_shipping_rates( $rates, $package ) {
   if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;

   $product_ids_with_local_pickup = array(); // 'click and pickup'
   
   // Loop through line items
   foreach( $package['contents'] as $line_item ) {
       // Get product id
       $product_id = $line_item['product_id'];
   
       if ( $line_item['sofs_cap'] ) { // indication it's reserved with 'Click and pick'
         array_push($product_ids_with_local_pickup, $product_id);
       }
   }
   
   if ( count($product_ids_with_local_pickup) > 0 ) {
       foreach ( $rates as $rate_key => $rate ) {
           if ( in_array( $rate->method_id, array( 'bring_fraktguiden:5800' ) ) ) { // default shipping method
               // how can i exclude the reserved products from being affected by this shipping method?
               $rates[$rate_key]->set_cost(/* cost */); // changing this affects ALL items, not just the specific ones
           }
       }
   }
   
   return $rates;
}

add_filter('woocommmerce_package_rates', 'cust_shipping_rates', 999, 2);
1
You need to split cart items into shipping packages using woocommerce_cart_shipping_packages hook, see this related threads and try to make yourself something.LoicTheAztec
Thanks alot! That's the type of filter i was looking for, the one that got iterated through by the shipping rates.Setz
Welcome! This is not so easy, but it is the right way… If you have any issue with your code, provide it in a new question adding some explanations and details…LoicTheAztec
The filter did its job. I only needed a way to exclude some cart items from being calculated towards shipping rates.:)Setz
You should answer your own question with the code that works for you (with a bit of explanations if needed), as it can help other readers.LoicTheAztec

1 Answers

0
votes

This answer does not explain how to add different shipping methods per products, this plugin can help with that - however, if you need to exclude certain cart items from being calculated towards shipping cost, this answer might help you.

The filter woocommerce_cart_shipping_packages returns an package, containing cart items that the shipping methods in the filter woocommerce_package_rates iterates over for calculating shipping costs.

Simply remove the cart items you dont wish shipping for by filtering the cart in the returned package like this:

// define the woocommerce_cart_shipping_packages callback 
function filter_woocommerce_cart_shipping_packages( $package ) { 
    $new_cart = $package[0]['contents'];

    foreach($cart as $cart_item) {
       // check for desired shipping method
       // cart items not checking for this property, will not be accounted for shipping costs
       if($cart_item['custom_extra_cart_item_data']) {
          array_push($new_cart, $cart_item); 
       } 
    }

    if(!empty($new_cart)) $package[0]['contents'] = $new_cart;

    return $package; 
}; 
         
// add the filter 
add_filter( 'woocommerce_cart_shipping_packages', 'filter_woocommerce_cart_shipping_packages', 10, 1 ); 

Here is code for my case (the keyname sofs_cap is an extra cart item data belonging to a unique cart item, indicating it was added with click and pick, your keyname can be whatever you like).

Note: in my case, i needed at least one shipping method in order to checkout. So when the cart consisted of only items not eligible for shipping - i simply sat the shipping method to 'Local pickup' (which was fitting as well). I also excluded that shipping method when the cart had items added both ways.

function custom_shipping_rates($rates, $pkg) {
   if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;

   $has_cap = false; // added with click and pikcup
   $has_def = false; // added default way
   
   // Loop through line items
   foreach( $pkg['contents'] as $line_item ) {
      if($line_item['sofs_cap']) $has_cap = true; else $has_def = true;
   }

   $new_rates = array();

   if($has_cap && $has_def || $has_def) { // cart with items added both ways returns all shipping methods except local pickup
      foreach ($rates as $rate_key => $rate ) {
         if($rate_key !== 'local_pickup:8') { // id can be different for your shipping method
            $new_rates[$rate_key] = $rate;
         }
     }

     return $new_rates;
   } else if ($has_cap) {  // cart with only items added with Click and pickup returns only shipping method local pickup
      $new_rates['local_pickup:8'] = $rates['local_pickup:8'];
      return $new_rates;
   } 

   return $rates;
}
add_filter('woocommerce_package_rates', 'custom_shipping_rates', 999, 2);

function custom_filtered_cart_shipping_packages( $shipping_package ) {
    $new_cart = array(); // only contains cart items that are not added with 'Click and pickup'
    $has_cap_added_items = false;
    
    foreach($shipping_package[0]['contents'] as $cart_item) {
        if(!$cart_item['sofs_cap']) {
            array_push($new_cart, $cart_item); // add only default added products to the package
        } else {
            $has_cap_added_items = true;
        }
    }

    $shipping_package[0]['contents'] = $new_cart;
      
    return $shipping_package; 
}
      
add_filter( 'woocommerce_cart_shipping_packages', 'custom_filtered_cart_shipping_packages');