In the Woocommerce store there are 3 shipping options
free shipping
flat rate
pickup
Inspired by Free shipping depending on weight and on minimal cart amount I would like to require a minimum total cart weight when choosing an shipping option.
When customers order for 24kg or less, they only should be able to pickup their order.
If the total cart weight is between 25kg and 49kg they should only may choose between the flat rate method or pickup.
And if the total cart weight is 50 kg and above they may choose between free shipping and pickup.
On the internet I found WooCommerce: How to Setup Tiered Shipping Rates by Order Amount, which I customized to work properly on the store because we have 2 shipping zones (so multiple flat rates, local pickup, etc).
Unfortunately it does not work for my needs, while I think the code is correct? Any help will be much appreciated.
add_filter( 'woocommerce_package_rates', 'bbloomer_woocommerce_tiered_shipping', 9999, 2 );
function bbloomer_woocommerce_tiered_shipping( $rates, $package ) {
if ( WC()->cart->get_cart_contents_weight() < 25 ) {
if ( isset( $rates['local_pickup:5'], $rates['local_pickup:9'] ) ) unset( $rates['flat_rate:3'], $rates['flat_rate:6'], $rates['free_shipping:1'], $rates['free_shipping:8'] );
} elseif ( WC()->cart->get_cart_contents_weight() < 50 ) {
if ( isset( $rates['flat_rate:3'], $rates['flat_rate:6'], $rates['local_pickup:5'], $rates['local_pickup:9'] ) ) unset( $rates['free_shipping:1'], $rates['free_shipping:8'] );
} else {
if ( isset( $rates['free_shipping:1'], $rates['free_shipping:8'], $rates['local_pickup:5'], $rates['local_pickup:9'] ) ) unset( $rates['flat_rate:3'], $rates['flat_rate:6'] );
}
return $rates;
}
Note: you cannot add multiple conditions to an if condition in the way you apply it.
Add the shipping options you want to keep under a certain condition to the $available_shipping array
When customers order for 24kg or less = local pickup
If the total cart weight is between 25kg and 49kg = local pickup and flat rate
If the total cart weight is 50 kg and above = local pickup and free shipping
function filter_woocommerce_package_rates( $rates, $package ) {
// Get cart contents weight
$weight = WC()->cart->get_cart_contents_weight();
// Conditions
if ( $weight <= 24 ) {
// Set available shipping options
$available_shipping = array( 'local_pickup' );
} elseif ( $weight > 24 || $weight < 50 ) {
$available_shipping = array( 'local_pickup', 'flat_rate' );
} else {
$available_shipping = array( 'local_pickup', 'free_shipping' );
}
foreach ( $rates as $rate_key => $rate ) {
// Targeting, NOT in array
if ( ! in_array( $rate->method_id, $available_shipping ) ) {
unset( $rates[$rate_key] );
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 10, 2 );
Related
I am trying to get woocommerce to remove all shipping classes and charge for just the two highest charges found within the cart but my function is not working. It is removing all the shipping charges and setting the shipping cost to zero.
EDIT: There is default functionality within a shipping zone to charge shipping classes set as "Per order: Charge shipping for the most expensive shipping class" but I need this to be "Per order: Charge shipping for the two most expensive shipping classes"
I have commented each step to show my understanding.
function charge_for_two_highest_shipping_classes( $rates ) {
// Sort rates by cost, in ascending order
usort( $rates, function( $a, $b ) {
return $a->cost - $b->cost;
} );
// Remove all shipping rates except the two highest
$highest_rates = array_slice( $rates, -2 );
// Set all other rates to zero
foreach ( $rates as $rate ) {
if ( ! in_array( $rate, $highest_rates, true ) ) {
$shipping_class_id = 0;
foreach ( $rate->get_meta_data() as $meta ) {
if ( 'shipping_class_id' === $meta->key ) {
$shipping_class_id = (int) $meta->value;
break;
}
}
$rate->cost = 0;
$rate->taxes = array();
$rate->set_meta_data( 'shipping_class_id', $shipping_class_id );
}
}
return $highest_rates;
}
add_filter( 'woocommerce_package_rates', 'charge_for_two_highest_shipping_classes', 10, 1 );
What you are trying to achieve is not as simple as you think.
The woocommerce_package_rates hook is wrong because all shipping class calculations are already done at this point.
The WooCommerce code you are looking for is here:
wp-content/plugins/woocommerce/includes/shipping/flat-rate/class-wc-shipping-flat-rate.php
See in the calculate_shipping function and look for the $highest_class_cost.
You could patch this function, but I wouldn't recommend it. There is no hook for this, you could only create new flat rates programmatically and do your logic there.
To sorts the rates in descending order and takes the first two elements (the two highest rates) and removes all other rates from the array. you can use this modification, Hope it might helpful to you 🙂 Thanks
function charge_for_two_highest_shipping_classes( $rates ) {
// Sort rates by cost, in descending order
usort( $rates, function( $a, $b ) {
return $b->cost - $a->cost;
} );
// Remove all shipping rates except the two highest
$highest_rates = array_slice( $rates, 0, 2 );
foreach ( $rates as $key => $rate ) {
if ( ! in_array( $rate, $highest_rates, true ) ) {
unset( $rates[$key] );
}
}
return $highest_rates;
}
add_filter( 'woocommerce_package_rates', 'charge_for_two_highest_shipping_classes', 10, 1 );
Currently, our store offers "Free Shipping up to $350", after thought on how to incorporate this into eCom - I believe this will be the simplest method. Fetch Shipping Cost --> IF Shipping Costs > $350 --> Apply "350-shipping" coupon code.
We use 2 different plugins to calculate shipping:
1.) WooCommerce FedEx Shipping
2.) WooCommerce WWE LTL Quotes
Looking to incorporate this inside of functions.php. Note, I am also working on a solution, so will apply here if I get to it sooner
Notice here the correct code for the Shipping Page: https://i.imgur.com/TndLiHT.png
So far I have the following:
// $350 Shipping Coupon
add_filter( 'woocommerce_package_rates', 'custom_shipping_rate_label_based_on_cost', 100, 2 );
function custom_shipping_rate_label_based_on_cost( $rates, $package ){
// Loop through available shipping rates
foreach ( $rates as $rate_key => $rate ) {
$rate_cost = $rate->cost;
if ( $rate_cost > 350 ) {
echo "<script type='text/javascript'>alert('350 Detected');</script>";
}
}
return $rates;
}
What is missing here is I need to say:
IF ALL Items in Foreach are greater than $350. Right now, if I run this script, I get an endless loop of "350 detected"
add_action( 'woocommerce_after_calculate_totals', 'apply_coupon_depending_on_shipping_total', 999, 1 );
function apply_coupon_depending_on_shipping_total( $cart ) {
$coupon_code = 'coupon_code';
if ( $cart->has_discount( $coupon_code ) ) {
return;
}
$shipping_total = $cart->get_shipping_total();
if ( $shipping_total > 350 ) {
$cart->apply_coupon( $coupon_code );
}
}
A friends ask me to add an extra fee on cart based on weight and only for specific categories (or excluding some categories, it doesn't matters).
The topic is, for summer, he wants to add ice into the packages to keep the products cold (ex. milk, cheese, etc).
He sells also gadgets and guided visits to his factory, etc so he doesn't want to apply the extra fee to those products.
Based on "Add custom fee based on total weight in Woocommerce" answer, my code version below, apply an extra fee to the whole cart, excluding the visit-product from this fee because the weight of the visit is obviously 0.
But I am not an expert with code and I don't know how to insert an array to include categories like 'milk' and 'cheese' (or viceversa to exclude 'visits' and 'gadgets').
In addiction, my code increase the fee by steps of 3kg (due to sizing of the packets by DHL/UPS/GLS etc)
/* Extra Fee based on weight */
add_action( 'woocommerce_cart_calculate_fees', 'shipping_weight_fee', 30, 1 );
function shipping_weight_fee( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Convert in grams
$cart_weight = $cart->get_cart_contents_weight() * 1000;
$fee = 0.00; // initial fee
// if cart is > 0 add €1,20 to initial fee by steps of 3000g
if( $cart_weight > 0 ){
for( $i = 0; $i < $cart_weight; $i += 3000 ){
$fee += 1.20;
}
}
// add the fee / doesn't show extra fee if it's 0
if ( !empty( $fee )) {
$cart->add_fee( __( 'Extra for ice' ), $fee, false );
}
}
Last question is: why the $i variable could be 0...1...1000000 without any change in the results? the code seems working exactly the same...
thanks
The following code is based on:
Predefined categories
Based on product weight (of products belonging to the predefined categories)
Fee increases by steps
(comment with explanation added in the code)
function shipping_weight_fee( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
/* SETTINGS */
// Specific categories
$specific_categories = array( 'categorie-1', 'categorie-2' );
// Initial fee
$fee = 1.20;
// Steps of kg
$steps_of_kg = 3;
/* END SETTINGS */
// Set variable
$total_weight = 0;
// Loop though each cart item
foreach ( $cart->get_cart() as $cart_item ) {
// Get product id
$product_id = $cart_item['product_id'];
// Get weight
$product_weight = $cart_item['data']->get_weight();
// NOT empty & has certain category
if ( ! empty( $product_weight ) && has_term( $specific_categories, 'product_cat', $product_id ) ) {
// Quantity
$product_quantity = $cart_item['quantity'];
// Add to total
$total_weight += $product_weight * $product_quantity;
}
}
if ( $total_weight > 0 ) {
$increase_by_steps = ceil( $total_weight / $steps_of_kg );
// Add fee
$cart->add_fee( __( 'Extra for ice' ), $fee * $increase_by_steps, false );
}
}
add_action( 'woocommerce_cart_calculate_fees', 'shipping_weight_fee', 10, 1 );
In Woocommerce I have the following situation: there are shipping methods before 100$, and after 100$ only one (free shipping) is available. So when a client buys a product 102$ and then apply the (10%) promo code, the price will be 91,80$. Because I unset the shipping methods after 100$ and the free shipping appears after 100$ only, for the client shows: "There are no shipping methods available..."
add_filter( 'woocommerce_package_rates', 'woocommerce_hide_shipping', 10, 2 );
function woocommerce_hide_shipping( $rates, $package ) {
$threshold = 100;
if ( WC()->cart->subtotal >= $threshold ) {
unset( $rates['flat_rate:45'] );
unset( $rates['flat_rate:75'] );
}
if ( WC()->cart->subtotal >= $threshold && !empty(WC()->cart->applied_coupons) ) {
//code here
}
return $rates;
}
Its possible to show the free shipping if the price was >100$ before promo code and a coupon was applied? I set up the free shipping to show a minimum order amount (100$) but there is a way to show, initialize? Other approaches are also welcomed.
The problem is.... Free Shipping that you have applied is bounded by min order and that is referring to order total (NOT SUBTOTAL).....
Whereas subtotal gives you the value without discount.
So,
Remove condition (min order total) from your Admin settings - Free shipping.... Now it will work for every order...
Then modify your code ---
add_filter( 'woocommerce_package_rates', 'woocommerce_hide_shipping', 10, 2 );
function woocommerce_hide_shipping( $rates, $package ) {
$threshold = 100;
if ( WC()->cart->subtotal >= $threshold ) {
unset( $rates['flat_rate:45'] );
unset( $rates['flat_rate:75'] );
}
if ( WC()->cart->subtotal < $threshold ) {
//code here
unset( $rates['free_shipping:45'] );
unset( $rates['free_shipping:75'] );
}
return $rates;
}
I've searched extensively to see if other folks had this situation and received an answer with no luck.
Essentially, I have two items customers can add to their cart. I want to make it so they cannot checkout with either of those items if their subtotal is not $15 or more.
Having the ability to just drop their IDs into the code would be fine. Or, I can assign them to the same category and set this minimum by category.
So far all I have is the basic PHP that sets a universal minimum. I just need some help figuring out how to limit this to meet my needs.
I'm a designer not a dev, so any help is much appreciated.
// Set a minimum dollar amount per order
add_action( 'woocommerce_check_cart_items', 'spyr_set_min_total' );
function spyr_set_min_total() {
// Only run in the Cart or Checkout pages
if( is_cart() || is_checkout() ) {
global $woocommerce;
// Set minimum cart total
$minimum_cart_total = 10;
// Total we are going to be using for the Math
// This is before taxes and shipping charges
$total = WC()->cart->subtotal;
// Compare values and add an error is Cart's total
// happens to be less than the minimum required before checking out.
// Will display a message along the lines of
// A Minimum of 10 USD is required before checking out. (Cont. below)
// Current cart total: 6 USD
if( $total <= $minimum_cart_total ) {
// Display our error message
wc_add_notice( sprintf( '<strong>A Minimum of %s %s is required before checking out.</strong>'
.'<br />Current cart\'s total: %s %s',
$minimum_cart_total,
get_option( 'woocommerce_currency'),
$total,
get_option( 'woocommerce_currency') ),
'error' );
}
}
}
2020 Update
Setting minimum value for some products categories or products ID's in cart for Cart and Checkout pages only.
This untested snippet made of this and this from wooThemes, and this too:
add_action( 'woocommerce_checkout_process', 'wc_minimum_order_amount' );
add_action( 'woocommerce_before_cart' , 'wc_minimum_order_amount' );
function wc_minimum_order_amount() {
## SETTINGS ##
$minimum_amount = 50; // Define a minimum order amount
$category_ids = array( 17, 18 ); // Define your category ids in the array (or an empty array to disable)
$product_ids = array( 64, 67, 78 ); // Define your product ids in the array (or an empty array to disable)
// Only on cart or checkout pages
if( WC()->cart->is_empty() || ! ( is_cart() || is_checkout() ) )
return; // Exit
$total_amount = WC()->cart->subtotal; // Items subtotal including taxes
if ( $total_amount < $minimum_amount ) {
$needs_minimum_amount = false; // Initializing
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product_id = $cart_item['product_id'];
$variation_id = $cart_item['variation_id'];
// 1. Check for matching product categories
if( sizeof($category_ids) > 0 ) {
$taxonomy = 'product_cat';
if ( has_term( $category_ids, $taxonomy, $product_id ) ) {
$needs_minimum_amount = true;
break; // Stop the loop
}
}
// 2. Check for matching product Ids
if( sizeof($product_ids) > 0 ) {
if ( array_intersect( $product_ids, array($product_id, $variation_id) ) ) {
$needs_minimum_amount = true;
break; // Stop the loop
}
}
}
if( $needs_minimum_amount ) {
wc_print_notice( sprintf(
__("You must have an order with a minimum of %s to place your order. Your current order total is %s."),
wc_price( $minimum_amount ),
wc_price( $total_amount )
), 'error' );
}
}
}
Code goes in functions.php file of your active child theme (active theme). Tested and works.
You can set your categories ID's, your products ID's and the minimum order value amount.
— — … I m p o r t a n t … N o t e … — —
This hooks are working for messages. But to avoid users from clicking you need to add Javascript/jQuery and maybe with ajax too (client side detection).