I calculate my flat rate shipping cost like this: x * [qty]
I also want to use this method for my local pickup methods but using the function like shown above, does not work.
How can I achieve a shipping cost calculation based on the item quantity?
WordPress: 4.8.4 / WooCommerce: 3.1.1
Link to the page: http://www.minimoto.me/
UPDATE:
After the first helpful answer, this is the code I'm using:
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
## ----- 1. Hiding shipping methods based on shipping class 92 ----- ##
// HERE define your shipping class to find
$class = 92;
// HERE define the shipping methods you want to hide
$method_key_ids = array('local_pickup:8');
// Checking in cart items
foreach( WC()->cart->get_cart() as $cart_item ){
// If we find the shipping class
if( $cart_item['data']->get_shipping_class_id() == $class ){
foreach( $method_key_ids as $method_key_id ){
unset($rates[$method_key_id]); // Remove the targeted methods
}
break; // Stop the loop
}
}
## ----- 2. Hiding shipping methods based on shipping class 132 ----- ##
// HERE define your shipping class to find
$class = 132;
// HERE define the shipping methods you want to hide
$method_key_ids = array('local_pickup:2', 'local_pickup:3', 'local_pickup:4');
// Checking in cart items
foreach( WC()->cart->get_cart() as $cart_item ){
// If we find the shipping class
if( $cart_item['data']->get_shipping_class_id() == $class ){
foreach( $method_key_ids as $method_key_id ){
unset($rates[$method_key_id]); // Remove the targeted methods
}
break; // Stop the loop
}
}
## ------- 3. Charge local pickup costs per item quantity ------- ##
$cart_items_count = WC()->cart->get_cart_contents_count(); // Cart items count
// Iterating through Shipping Methods
foreach ( $rates as $rate_key => $rate ) {
$method_id = $rate_values->method_id;
$rate_id = $rate_values->id;
// For "Local pickup" Shipping" Method only
if ( 'local_pickup' === $method_id ) {
if( ! empty( $rates[$rate_id]->cost && $rates[$rate_id]->cost > 0 ) ) {
// Set the rate calculated cost based on cart items count
$rates[$rate_id]->cost = number_format($rates[$rate_id]->cost * $cart_items_count, 2);
// Taxes rate cost (if enabled)
foreach ($rates[$rate_id]->taxes as $key => $tax){
if( $rates[$rate_id]->taxes[$key] > 0 ){ // set the new tax cost
$taxes[$key] = number_format( $rates[$rate_id]->taxes[$key] * $cart_items_count, 2 );
$has_taxes = true;
} else {
$has_taxes = false;
}
}
if( $has_taxes )
$rates[$rate_id]->taxes = $taxes;
}
}
}
return $rates;
}
You can make this in your existing customized code from this answer, using the custom function hooked in woocommerce_package_rates action hook.
With the following code, Your local pickup shipping methods costs will be multiplied by the total cart items count:
add_filter( 'woocommerce_package_rates', 'customizing_shipping_methods', 10, 2 );
function customizing_shipping_methods( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
## ----- 1. Hiding shipping methods based on shipping class ----- ##
// HERE define your shipping class to find
$class = 92;
// HERE define the shipping methods you want to hide
$method_key_ids = array('flat_rate:7', 'local_pickup:3');
// Checking in cart items
foreach( WC()->cart->get_cart() as $cart_item ){
// If we find the shipping class
if( $cart_item['data']->get_shipping_class_id() == $class ){
foreach( $method_key_ids as $method_key_id ){
unset($rates[$method_key_id]); // Remove the targeted methods
}
break; // Stop the loop
}
}
## ------- 2. Charge local pickup costs per item quantity ------- ##
$cart_items_count = WC()->cart->get_cart_contents_count(); // Cart items count
// Iterating through Shipping Methods
foreach ( $rates as $rate_key => $rate_values ) {
$method_id = $rate_values->method_id;
$rate_id = $rate_values->id;
// For "Local pickup" Shipping" Method only
if ( 'local_pickup' === $method_id ) {
if( ! empty( $rates[$rate_id]->cost && $rates[$rate_id]->cost > 0 ) ) {
// Set the rate calculated cost based on cart items count
$rates[$rate_id]->cost = number_format($rates[$rate_id]->cost * $cart_items_count, 2);
// Taxes rate cost (if enabled)
foreach ($rates[$rate_id]->taxes as $key => $tax){
if( $rates[$rate_id]->taxes[$key] > 0 ){ // set the new tax cost
$taxes[$key] = number_format( $rates[$rate_id]->taxes[$key] * $cart_items_count, 2 );
$has_taxes = true;
} else {
$has_taxes = false;
}
}
if( $has_taxes )
$rates[$rate_id]->taxes = $taxes;
}
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works
Sometimes, you should may be need to refresh shipping methods going to shipping areas, then disable / save and re-enable / save your "flat rate" and "local pickup" shipping methods.
Related
Based on Add an additional cost to flat rate shipping each 3 items in Woocommerce answer code, I have made some changes to add an additional cost to flat are shipping method each 2 items (instead of each 3 items) and only when there are exclusively items from a specific category (here "T-Shirts" category).
Here's my code attempt:
// add X amount to shipping for every 2 items added to the cart (flat rate only)
add_filter('woocommerce_package_rates', 'shipping_additional_cost_each_three_items', 100, 2);
function shipping_additional_cost_each_three_items( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// HERE set your additional shipping cost
$additional_cost = 8.40;
$items_count = WC()->cart->get_cart_contents_count();
// Define/replace here your correct category slug (!)
$product_category = 't-shirts';
$prod_cat = false;
// Going through each item in cart to see if there is anyone of your category
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product_id = $cart_item['product_id'];
if ( has_term( $product_category, 'product_cat', $product_id ) ){
$prod_cat = true;
}
}
// Loop through the shipping taxes array
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
// Targetting "flat rate"
if( 'flat_rate' === $rate->method_id ){
// Get the initial cost
$initial_cost = $new_cost = $rates[$rate_key]->cost;
// Adding the additional cost if product in T-Shirt category after every 2 items (3, 6, 9 …)
if ( $prod_cat ) {
for($i = 0; $i <= $items_count; $i+=3){
$new_cost += $additional_cost;
}
}
// Set the new cost
$rates[$rate_key]->cost = $new_cost;
// Taxes rate cost (if enabled)
$taxes = [];
// Loop through the shipping taxes array (as they can be many)
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 ){
// Get the initial tax cost
$initial_tax_cost = $new_tax_cost = $rates[$rate_key]->taxes[$key];
// Get the tax rate conversion
$tax_rate = $initial_tax_cost / $initial_cost;
// Set the new tax cost
$taxes[$key] = $new_cost * $tax_rate;
$has_taxes = true; // Enabling tax
}
}
}
if( $has_taxes )
$rates[$rate_key]->taxes = $taxes;
return $rates;
}
}
For "Flat rate" chosen shipping method, the code works just fine when items from "t-shirts" category are in the cart. But if there is an item that doesn't belongs to "t-shirt" category, the rate just disappears and shows the title with no amount.
Can someone tell me where I have to place the condition 'if category is X', to make that code functional?
There are some mistakes in your code (like return $rates; that should need to be just before last closing bracket). Try the following revisited code instead:
add_filter('woocommerce_package_rates', 'shipping_additional_cost_each_three_items', 100, 2);
function shipping_additional_cost_each_three_items( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// HERE set your additional shipping cost
$additional_cost = 8.40;
$each_items = 2; // Number of items (for additional cost)
// Her set your category(ies) (can be term Ids slugs or names)
$product_categories = array('t-shirts');
$items_cat_count = 0; // Initializing
// Loop through cart items for the current shipping package
foreach( $package['contents'] as $cart_item ) {
if ( ! has_term( $product_categories, 'product_cat', $cart_item['product_id'] ) ){
$items_cat_count += $cart_item['quantity']; // Count items from defined category
}
}
if ( $items_cat_count >= $each_items ) {
// Loop through the shipping taxes array
foreach ( $rates as $rate_key => $rate ){
// Targetting "flat rate"
if( 'flat_rate' === $rate->method_id ){
$initial_cost = $new_cost = $rate->cost; // Get the initial cost
$has_taxes = false; // Initializing
$taxes = array(); // Initializing
// Adding to cost the additional cost each 2 items (2, 4, 6 …)
for($i = 0; $i <= $items_cat_count; $i += $each_items){
$new_cost += $additional_cost;
}
$rates[$rate_key]->cost = $new_cost; // Set the new cost
// Taxes rate cost (if any) - Loop through taxes array (as they can be many)
foreach ($rate->taxes as $key => $tax){
if( $tax > 0 ){
// Get the initial tax cost
$initial_tax_cost = $new_tax_cost = $tax;
// Get the tax rate conversion
$tax_rate = $initial_tax_cost / $initial_cost;
// Set the new tax cost in the array
$taxes[$key] = $new_cost * $tax_rate;
$has_taxes = true; // Enabling tax changes
}
}
// set array of shipping tax cost
if( $has_taxes ) {
$rates[$rate_key]->taxes = $taxes;
}
}
}
}
return $rates;
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Don't forget to empty your cart to refresh shipping cached data.
Based on Set custom shipping rates programmatically in Woocommerce 3 answer code, I modified it in order to add a discount on shipping rates for each seller.
add_filter( 'woocommerce_package_rates', 'custom_shipping_methods', 10, 2 );
function custom_shipping_methods( $rates, $package ) {
$reduction_cost_percentage = 30; // Discount percentage
foreach( WC()->cart->get_cart() as $cart_item ){
$in_cart_product_id = $cart_item['product_id'];
$cart_seller_id = get_post_field('post_author', $in_cart_product_id);
$cart_seller_meta = get_userdata($cart_seller_id);
$cart_seller_roles = $cart_seller_meta->roles;
if($cart_seller_roles[0] == 'seller'){
foreach( $rates as $rate_key => $rate ){
if( $rate->method_id != 'free_shipping'){
$rates[$rate_key]->cost = $rates[$rate_key]->cost * ((100-$reduction_cost_percentage) / 100);
return $rates;
}
}
}
}
}
Now I want to exclude this discount from a certain role for example seller_2. How can I do this?
There are some mistakes and oversights in your code, try the following instead:
add_filter( 'woocommerce_package_rates', 'custom_shipping_methods', 10, 2 );
function custom_shipping_methods( $rates, $package ) {
// Loop through cart items for the current package
foreach( $package['contents'] as $cart_item ){
$seller_id = get_post_field('post_author', $cart_item['product_id']);
$seller_data = get_userdata($seller_id);
// Excluding product 'seller' user role
if ( ! empty($seller_data) && is_array($seller_data->roles) && in_array('seller', $seller_data->roles) ) {
return $rates; // stop the loop and return normally the shipping rates
}
}
$percentage = 30; // <== Set your discount percentage
$discount_rate = $percentage / 100;
// Loop through shipping rates for the current package when seller user role is not found
foreach( $rates as $rate_key => $rate ){
// Not for free shipping
if( $rate->method_id != 'free_shipping' ){
// Change rate cost
$rates[$rate_key]->cost = $rate->cost * $discount_rate;
$taxes = array(); // Initializing
// change taxes rate cost (if enabled)
foreach ($rate->taxes as $key => $tax){
if( $tax > 0 ){
$taxes[$key] = $tax * $discount_rate;
$has_taxes = true;
}
}
// Change taxes cost
if( $has_taxes ) {
$rates[$rate_key]->taxes = $taxes;
}
}
}
return $rates;
}
Code goes in function.php file of your active child theme (active theme). It should works.
Clearing shipping caches:
You will need to empty your cart, to clear cached shipping data
Or In shipping settings, you can disable / save any shipping method, then enable back / save.
In woocommerce regarding Shipping methods, I am trying to have the following:
Products A only in cart: set with "Free shipping"
Products B only in cart: set with:
Flat rate amount of 15 if Products B purchased amount is less than 200
Free shipping if Products B purchased amount reaches 200 or more..
Products A + Products B are in cart at the same time: "Free Shipping" without any amount restriction.
I have tried by using flat rate and shipping classes I am getting like if product A and product B is there then if the cart doesn't reach 200 it is taking 15 shipping charge.
Any help is appreciated.
Updated: To make it work first you will need:
To add a "Free" shipping class (first),
To enable 2 shipping methods: "Free shipping" and "Flat rate",
In your products with Free shipping will need to be set with the shipping class "Free",
In your other product will not have any defined shipping class.
For the "free shipping" method, you will not add any restrictions amount to it.
For the "Flat rate" shipping method, you will set it as in this screen shot:
The magic will be done by the following code that will make the rest:
add_filter('woocommerce_package_rates', 'conditional_free_shipping', 10, 2);
function conditional_free_shipping( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
## -- Your settings below -- ##
$shipping_class = 'free'; // "Free" shipping class products
$min_free_amount = 200; // Minimal Free shipping amount for normal products
## -- -- -- -- -- -- -- -- -- ##
$has_free = false; // Initializing
$products_total = 0; // Initializing
// Loop through cart items
foreach( $package['contents'] as $cart_item ) {
if( $cart_item['data']->get_shipping_class() == $shipping_class ) {
$has_free = true;
} else {
// Get the total purchased amount for normal product
$products_total += $cart_item['line_total'] + $cart_item['line_tax'];
}
}
foreach ( $rates as $rate_key => $rate ){
// 1. Only Free shipping products in cart OR both products kind in cart
if( $has_free ) {
if( 'flat_rate' === $rate->method_id )
unset( $rates[$rate_key] ); // Remove flat rate
}
// 2. Only normal products in cart
else {
// A. If it's under the min amount
if( 'free_shipping' === $rate->method_id && $products_total < $min_free_amount )
unset( $rates[$rate_key] ); // Remove Free shipping
// B. When min amount is reached
elseif( 'flat_rate' === $rate->method_id && $products_total >= $min_free_amount )
unset( $rates[$rate_key] ); // Remove flat rate
}
}
return $rates;
}
Code goes in function.php file of your active child theme (active theme). Tested and works.
You might need to refresh shipping cached data: disable, save and enable, save related shipping methods for the current shipping zone, in Woocommerce shipping settings.
I did some improvement in the code now it is working fine..
add_filter('woocommerce_package_rates', 'conditional_free_shipping', 10, 2);
function conditional_free_shipping( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
## -- Your settings bellow -- ##
$shipping_class = 'free'; // "Free" shipping class products
$min_free_amount = 200; // Minimal Free shipping amount for normal products
## -- -- -- -- -- -- -- -- -- ##
$has_normal = $has_free = false; // Initializing
$products_total = 0; // Initializing
// Loop through cart items
foreach( $package['contents'] as $cart_item ) {
if( $cart_item['data']->get_shipping_class() == $shipping_class ) {
$has_free = true;
} else {
$has_normal = true;
// Get the total purchased amount for normal product
$products_total += $cart_item['line_total'] + $cart_item['line_tax'];
}
}
foreach ( $rates as $rate_key => $rate ){
// 1. Only Free shipping products in cart
if( $has_free && ! $has_normal ) {
if( 'flat_rate' === $rate->method_id )
unset( $rates[$rate_key] ); // Remove flat rate
}
elseif(( $has_free && $has_normal )){
if( 'flat_rate' === $rate->method_id && $products_total <= $min_free_amount )
unset( $rates[$rate_key] );
}
// 2. Only normal products in cart OR Both products kind in cart
elseif( ( ! $has_free && $has_normal ) ) {
// A. If it's under the min amount
if( 'free_shipping' === $rate->method_id && $products_total < $min_free_amount )
unset( $rates[$rate_key] ); // Remove Free shipping
// B. When min amount is reached
elseif( 'flat_rate' === $rate->method_id && $products_total >= $min_free_amount )
unset( $rates[$rate_key] ); // Remove flat rate
}
}
return $rates;
}
We're selling sample products on our Woocommerce site, which is just a variable product. The product has a unique shipping class that allows it to be delivered for 1.99.
Actually this cost is always set when if item belongs to that unique shipping class, even if there is other items.
I would like if possible to enable that shipping cost only if that specific item (from that unique shipping class) is alone in cart.
Any help is appreciated.
The following hooked function will set the shipping cost to 0 if an items with a specific shipping class are melted to other items:
add_filter('woocommerce_package_rates', 'conditional_shipping_class_cost', 15, 2);
function conditional_shipping_class_cost( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// HERE define the targeted shipping method
$shipping_class = 'Extra';
// Initializing variables
$found = $others = false;
// Loop through cart items and checking for the specific product
foreach( $package['contents'] as $item ) {
if( $item['data']->get_shipping_class() == sanitize_title($shipping_class) ){
$found = true; // Has the shipping class
} else {
$others = true; // NOT the shipping class
}
}
// When items with the defined shipping are not alone in cart
if( $found && $others ){
// Loop through shipping rates
foreach ( $rates as $rate_key => $rate ){
// For Flat rate and Local pickup shipping methods
if( $rate->method_id == 'flat_rate' ) {
// Set the cost to zero
$rates[$rate_key]->cost = 0;
$rates[$rate_key]->label = 'f: '.$found.' | o: '.$others.' ';
// Initializing variables
$has_taxes = false;
$taxes = [];
// Loop through the shipping taxes array (as they can be many)
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 ){
// Set the tax cost to zero
$taxes[$key] = 0;
$has_taxes = true;
}
}
// Set new taxes cost array
if( $has_taxes )
$rates[$rate_key]->taxes = $taxes;
}
}
}
return $rates;
}
This code goes on function.php file of your active child theme (or theme). Tested and works.
I have a specific coupon which is special50. When someone applied this coupon on the store then a new shipping method need to add. When current shipping method price is $50 (flat rate) and after applying coupon new shipping method, pricing will be $25. In a word, if you apply this coupon you will receive 50% OFF on products(which WooCommerce has already provided to us) and 50% OFF on shipping(which really I need).
add_action( 'woocommerce_flat_rate_shipping_add_rate', 'add_another_custom_flat_rate', 10, 2 );
function add_another_custom_flat_rate( $method, $rate ) {
$new_rate = $rate;
$new_rate['id'] .= ':' . 'custom_rate_name';
$new_rate['label'] = 'Shipping and handling';
global $woocommerce, $wpdb;
$coupon = "SELECT post_title FROM {$wpdb->posts} WHERE post_title='special50' AND post_type ='shop_coupon' AND post_status ='publish'";
if(in_array($coupon_id, $woocommerce->cart->applied_coupons)){
$cost = 25;
}
$new_rate['cost'] = $cost;
$method->add_rate( $new_rate );
}
This can be done using the following custom function hooked in woocommerce_package_rates filter hook, without any need of creating an additional discounted flat rate. The following code will change the "flat rate" shipping method cost when 'special50' coupon is applied.
You should first "Enable debug mode" in Woocommerce settings > shipping > Shipping options.
The code:
add_filter('woocommerce_package_rates', 'coupon_discount_on_flat_rate', 10, 2);
function coupon_discount_on_flat_rate( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// Checking for 'special50' in applied coupons
if( in_array( 'special50', WC()->cart->get_applied_coupons() ) ){
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
// Targeting "flat rate" shipping method
if( $rate->method_id === 'flat_rate' ){
// Set 50% of the cost
$rates[$rate_key]->cost = $rates[$rate_key]->cost / 2;
// Taxes rate cost (if enabled)
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 ){
$has_taxes = true;
// set 50% of the cost
$taxes[$key] = $rates[$rate_key]->taxes[$key] / 2;
}
}
if( $has_taxes )
$rates[$rate_key]->taxes = $taxes;
}
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Dont forget to disable "Enable debug mode" once this has been tested and works.