I recently tried to modify all my shipping rates with hook to apply discount.
Here's my code :
add_filter( 'woocommerce_package_rates', 'woocommerce_package_rates' );
function woocommerce_package_rates( $rates ) {
$user_id = get_current_user_id();
if ( ! wc_memberships_is_user_active_member( $user_id, 'silver' ) ) { return $rates; }
$discount_amount = 30; // 30%
foreach($rates as $key => $rate ) {
$rates[$key]->cost = $rates[$key]->cost - ( $rates[$key]->cost * ( $discount_amount/100 ) );
}
return $rates;
}
But one more step is the tax ! I got wrong tax.
For example I have my shipping rate who cost 3$. With the discount, it's now 2,10$.
I buy one item for 2$ and the shipping 2.10$.
I got 1$ for the tax (as the 3$ shipping cost. look like he doesn't take the changes) and normally it's 0.82$.
What do I need to get the correct tax calculation?
Update: related to tax cost calculation for the shipping methods
There is some little errors on your code and you have missed the tax calculation discount. I have revisited your code a bit, you should try this:
add_filter( 'woocommerce_package_rates', 'conditional_shipping_discount', 10, 2 );
function conditional_shipping_discount( $rates, $packages ) {
$user_id = get_current_user_id();
if ( ! wc_memberships_is_user_active_member( $user_id, 'silver' ) ) return $rates;
$percent = 30; // 30%
$discount = 1 - ($percent / 100);
foreach($rates as $rate_key => $rate_values ) {
// Get original cost
$original_cost = $rates[$rate_id]->cost;
// Calculate the discounted rate cost
$new_cost = $original_cost * $discount;
// Set the discounted rate cost
$rates[$rate_key]->cost = number_format(new_cost, 2);
// calculate the conversion rate (for taxes)
$conversion_rate = $new_cost / $original_cost;
// Taxes rate cost (if enabled)
$taxes = array();
foreach ($rate->taxes as $key => $tax){
if( $tax > 0 ){ // set the new tax cost
// set the new line tax cost in the taxes array
$taxes[$key] = number_format( $tax * $conversion_rate, 2 );
}
}
// Set the new taxes costs
$rates[$rate_key]->taxes = $taxes
}
return $rates;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested and works.
You should need to refresh the shipping caches:
First this code is already saved on your function.php file.
In Shipping settings, enter in a Shipping Zone and disable a Shipping Method and "save". Then re-enable that Shipping Method and "save". You are done.
Below code #LoicTheAztec without mistakes:
add_filter( 'woocommerce_package_rates', 'conditional_shipping_discount', 10, 2 );
function conditional_shipping_discount( $rates, $packages ) {
$user_id = get_current_user_id();
if ( ! wc_memberships_is_user_active_member( $user_id, 'silver' ) ) return $rates;
$percent = 30; // 30%
$discount = 1 - ($percent / 100);
foreach($rates as $rate_key => $rate_values ) {
// Get original cost
$original_cost = $rates[$rate_key]->cost;
// Calculate the discounted rate cost
$new_cost = $original_cost * $discount;
// Set the discounted rate cost
$rates[$rate_key]->cost = number_format($new_cost, 2);
// calculate the conversion rate (for taxes)
$conversion_rate = $new_cost / $original_cost;
// Taxes rate cost (if enabled)
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $tax > 0 ){ // set the new tax cost
// set the new line tax cost in the taxes array
$taxes[$key] = number_format( $tax * $conversion_rate, 2 );
}
}
// Set the new taxes costs
$rates[$rate_key]->taxes = $taxes;
}
return $rates;
}
The problem with the answers above is that you're calculating the tax based on the calculations already made for the original cost. What if, for example, a plugin is calculating the tax based on the shipping cost? For example, 10% if $100 or above, 20% if $200 or above, 0% if less than $100.
If the original cost was $150 and you're applying a $60 discount, the discounted cost would be $90 and no tax would apply in the aforementioned scenario. So we need a way to modify the cost before the tax is calculated. This way we don't have to recalculate the tax ourselves and we reduce bugs like the example in the first paragraph.
For one of my plugins I'm using the woocommerce_shipping_method_add_rate_args filter, which is called by \WC_Shipping_Method::add_rate(). This method is called everytime a \WC_Shipping_Method instance adds a rate.
This filter will accept an array as its first argument, which is the rate data. This array has a 'cost' entry which is what we want to modify. Keep in mind that this can be a scalar (apparently, a numeric string) or an array. Just check if it's an array when dealing with this:
add_filter('woocommerce_shipping_method_add_rate_args', function(array $rateArguments) : array {
// Total up the cost. Taken from the WooCommerce source code. woocommerce/includes/abstracts/abstract-wc-shipping-method.php
$totalCost = is_array( $rateArguments['cost'] ) ? array_sum( $rateArguments['cost'] ) : $rateArguments['cost'];
$rateArguments['cost'] = 'here the final cost';
return $rateArguments;
});
Related
With following code I'm able for specific shipping rate method (here 'easypack_parcel_machines') to set the cost to 0, when cart subtotal is up to a specific amount (here 150 PLN):
function override_inpost_cost( $rates, $package ) {
// Make sure paczkomaty is available
if ( isset( $rates['easypack_parcel_machines'] ) ) {
// Current value of the shopping cart
$cart_subtotal = WC()->cart->subtotal;
// Check if the subtotal is greater than 150pln
if ( $cart_subtotal >= 150 ) {
// Set the cost to 0pln
$rates['easypack_parcel_machines']->cost = 0;
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'override_inpost_cost', 10, 2 );
But the problem is that shipping tax original cost remains, even if 'easypack_parcel_machines' shipping method rate cost is set to zero its original cost (as it is taxable).
How to change the code so if 'easypack_parcel_machines' shipping method rate cost is set to 0 the tax will also be set to zero?
Note: As cart can be split into multiple shipping packages by some plugins or some custom code, the correct way is to get the subtotal of related cart items included in the current shipping package.
What is missing in your code, is to set the taxes to zero as follows:
add_filter( 'woocommerce_package_rates', 'override_inpost_shipping_method_cost', 10, 2 );
function override_inpost_shipping_method_cost( $rates, $package ) {
$targeted_shipping_rate_id = 'easypack_parcel_machines'; // <== Define shipping method rate Id
// Make sure that our shipping rate is available
if ( isset( $rates[$targeted_shipping_rate_id] ) ) {
$cart_subtotal_incl_tax = 0; // Initializing
// Get cart items subtotal for the current shipping package
foreach( $package['contents'] as $cart_item ) {
$cart_subtotal_incl_tax += $cart_item['line_subtotal'] + $cart_item['line_subtotal_tax'];
}
// Check if the subtotal is greater than 150pln
if ( $cart_subtotal_incl_tax >= 150 ) {
// Set the cost to 0pln
$rates[$targeted_shipping_rate_id]->cost = 0;
$taxes = array(); // Initializing
// Loop through the shipping method rate taxes array
foreach( $rates[$targeted_shipping_rate_id]->taxes as $key => $tax_cost ) {
$taxes[$key] = 0; // Set each tax to Zero
}
if ( ! empty($taxes) ) {
$rates[$targeted_shipping_rate_id]->taxes = $taxes; // Set back "zero" taxes array
}
}
}
return $rates;
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Note: Don't forget to empty your cart to refresh shipping cached data.
In Woocommerce, I would like to have a function that I can include within my theme that adds shipping rate based on price and weight.
if price is above 20USD shipping is free, below Shipping cost : 3USD
if weight is above 10kg shipping fee is 2USD extra
Based on "Shipping calculated on the items weight and cart amount" answer thread, I tried something like the beneath code:
//Adding a custom Shipping Fee to cart based conditionally on weight and cart amount
add_action('woocommerce_cart_calculate_fees', 'custom_conditional_shipping_fee', 10, 1);
function custom_conditional_shipping_fee( $cart_object ){
#### SETTINGS ####
// Your targeted "heavy" product weight
$target_weight = 1;
// Your targeted cart amount
$target_cart_amount = 20;
// Price by Kg;
$price_kg = 2;
// Amount set in 'flat rate' shipping method;
$flat_rate_price;
// Initializing variables
$fee = 0;
$calculated_weight = 0;
// For cart SUBTOTAL amount EXCLUDING TAXES
WC()->cart->subtotal_ex_tax >= $target_cart_amount ? $passed = true : $passed = false ;
// For cart SUBTOTAL amount INCLUDING TAXES (replace by this):
// WC()->cart->subtotal >= $target_cart_amount ? $passed = true : $passed = false ;
// Iterating through each cart items
foreach( $cart_object->get_cart() as $cart_item ){
// Item id ($product ID or variation ID)
if( $cart_item['variation_id'] > 0)
$item_id = $cart_item['variation_id'];
else
$item_id = $cart_item['product_id'];
// Getting the product weight
$product_weight = get_post_meta( $item_id , '_weight', true);
// Line item weight
$line_item_weight = $cart_item['quantity'] * $product_weight;
// When cart amount is up to 1kg, Adding weight of heavy items
if($passed && $product_weight < $target_weight)
$calculated_weight += $line_item_weight;
}
#### Making the fee calculation ####
// Cart is up to 250 with heavy items
if ( $passed && $calculated_weight != 0 ) {
// Fee is based on cumulated weight of heavy items
$fee = ($calculated_weight * $price_kg) - $flat_rate_price;
}
// Cart is below 250
elseif ( !$passed ) {
// Fee is based on cart total weight
$fee = ($cart_object->get_cart_contents_weight( ) * $price_kg) - $flat_rate_price;
}
#### APPLYING THE CALCULATED FEE ####
// When cart is below 250 or when there is heavy items
if ($fee > 0){
// Rounding the fee
$fee = round($fee);
// This shipping fee is taxable (You can have it not taxable changing last argument to false)
$cart_object->add_fee( __('Shipping weight fee', 'woocommerce'), $fee, true);
}
}
Edit:
And at the same time I want it to show this immediately on the cart page. Now it is showing "enter address to view shipping options". Basically just look at total of cart and show either a rate or free shipping based on the rules described for weight and price.
woocommerce_package_rates is the right filter to customize the shipping rate.
You can achieve this by following way.
Step-1: Create two shipping method, Free shipping and Flat rate with coast 3$
Step-2: Copy and paste below code snippet into functions.php
Config the flat rate and free shipping properly on the snippet.
add_filter( 'woocommerce_package_rates', 'modify_shipping_rate', 15, 2 );
function modify_shipping_rate( $available_shipping_methods, $package ){
global $woocmmerce;
$total_weight = WC()->cart->cart_contents_weight;
$total_coast = WC()->cart->get_cart_contents_total();
if( $total_coast >= 20 ){
unset($available_shipping_methods['flat_rate:1']); //Remove flat rate for coat abobe 20$
}elseif( $total_weight > 10 ){
unset($available_shipping_methods['free_shipping:1']); // remove free shipping for below 20$
$available_shipping_methods['flat_rate:1']->cost += 2; // add 2$ if weight exceeds 10KG
}else{
unset($available_shipping_methods['free_shipping:1']); // remove free shipping for below 20$
}
return $available_shipping_methods;
}
Use below snippet to change the default cart message.
add_filter( 'woocommerce_cart_no_shipping_available_html', 'change_msg_no_available_shipping_methods', 10, 1 );
add_filter( 'woocommerce_no_shipping_available_html', 'change_msg_no_available_shipping_methods', 10, 1 );
function change_msg_no_available_shipping_methods( $default_msg ) {
$custom_msg = "Enter address to view shipping options";
if( empty( $custom_msg ) ) {
return $default_msg;
}
return $custom_msg;
}
The following code isn't based on a custom fee, but on shipping methods customizations.It requires to set in shipping settings, for each shipping zone:
A "Flat Rate" with a defined cost ($3 for you)
A "Free Shipping" with no requirements (no restrictions).
The code will handle flat rate calculation cost based on the weight and also the tax calculations.
The code will work for any shipping zone without needing to define in the code the shipping method IDs.
Here is the code:
add_filter( 'woocommerce_package_rates', 'filter_package_rates_callback', 10, 2 );
function filter_package_rates_callback( $rates, $package ) {
## -------- Settings -------- ##
$targeted_total = 20; // The targeted cart amount
$weight_threshold = 10; // The cart weight threshold
$extra_for_10kg = 2; // 10 Kg addition extra cost;
$total_weight = WC()->cart->get_cart_contents_weight();
$cart_subtotal = WC()->cart->get_subtotal(); // Excluding taxes
// Set shipping costs based on weight
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
if( $cart_subtotal < $targeted_total || $total_weight >= $weight_threshold ){
// Remove Free shipping Method
if( 'free_shipping' === $rate->method_id ) {
unset( $rates[$rate_key] );
}
// Flat rate calculation cost when 10 kg weight is reached
if( 'flat_rate' === $rate->method_id && $total_weight >= $weight_threshold ) {
// The default rate cost (set in the shipping method)
$default_cost = $rate->cost;
// The new calculated cost (up to 10 kg)
$new_cost = $default_cost + $extra_for_10kg;
// Tax rate conversion (for tax calculations)
$tax_rate_converion = $new_cost / $default_cost;
// Set the new cost
$rates[$rate_key]->cost = $new_cost;
// TAXES RATE COST (if enabled)
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $tax > 0 ){
// New tax calculated cost
$taxes[$key] = $tax * $tax_rate_converion;
$has_taxes = true;
}
}
// Set new taxes cost
if( $has_taxes )
$rates[$rate_key]->taxes = $taxes;
}
} else {
// Remove Flat Rate methods (keeping Free Shipping Method only)
if( 'flat_rate' === $rate->method_id ) {
unset( $rates[$rate_key] );
}
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Refresh the shipping caches: (required)
1) This code is already saved on your function.php file.
2) In a shipping zone settings, disable / save any shipping method, then enable back / save.
You are done and you can test it.
Displaying shipping methods directly (Regarding your question edit)
The provided information in your question is not enough to see how this can be managed.
If you are selling in one country, and you have a unique shipping zone, you can force the country for unlogged customers to get the shipping methods displayed, using the following:
add_action( 'template_redirect', 'allow_display_shipping_methods' );
function allow_display_shipping_methods() {
// HERE define the targeted country code
$country_code = 'GB';
// Set the shipping country if it doesn't exist
if( ! WC()->customer->get_shipping_country() )
WC()->customer->set_shipping_country('GB');
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Now this is another question, in your initial question and should be asked as a new question.
Related answers:
Set a custom shipping rate cost calculated on cart total weight in WooCommerce
Free shipping depending on weight and on minimal cart amount
Shipping cost based on cart total weight in Woocommerce 3
Hide and unhide a specific shipping methods based on shipping class in WooCommerce
i'm looking for a solution that allows me to change the cost of every shipping zones because i need to have a rounded value also in my second currency (from € to yen for Japan). I used WC_Geolocation::geolocate_ip() to change the currency dynamically based on the IP adress, but i can't find a solution to change the shipping zones cost. An example to explain:
$location = WC_Geolocation::geolocate_ip();
$country = $location['country'];
if($country === "JP"){
//do some code to change every shipping zone cost with a custom value
//(different for every shipping zone)
}
I hope I was clear in my explanation.
Updated
You may have to "Enable debug mode" in general shipping settings under "Shipping options" tab, to disable temporarily shipping caches.
The following code will change the shipping method cost on custom calculations for a specific country (here Japan):
add_filter( 'woocommerce_package_rates', 'custom_shipping_rates_based_on_country', 20, 2 );
function custom_shipping_rates_based_on_country( $rates, $package ) {
// ONLY for Japan
if( $package['destination']['country'] !== 'JP' )
return $rates;
// Loop through shipping methods rates
foreach( $rates as $rate_key => $rate ){
// Excluding free shipping method
if( $rate->method_id !== 'free_shipping') {
// Get initial shipping rate cost
$initial_cost = $rate->cost;
## ---- START ------------------------------------------- ##
// Add your calculations and settings
// Rounding decimal setting
$rounding_decimals = 2;
// Conversion rate
$conversion_rate = 1.25;
// New calculated cost
$new_cost = $initial_cost * $conversion_rate;
## ---- END ------------------------------------------- ##
// Set the rate cost (rounded with 2 decimals)
$rates[$rate_key]->cost = round( $new_cost, $rounding_decimals );
// Taxes rate cost (if enabled)
foreach ($rate->taxes as $key => $tax){
if( $rate->taxes[$key] > 0 ){
// Calculating the tax rate unit
$tax_rate = $rate->taxes[$key] / $initial_cost;
// Calculating the new tax cost
$new_tax_cost = $tax_rate * $new_cost;
// set the new tax cost
$taxes[$key] = round( $new_tax_cost, $rounding_decimals );
$has_taxes = true;
} else {
$has_taxes = false;
}
}
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.
Don't forget to enable back shipping cache.
I have a WordPress site which uses WooCommerce plugin. I would like to offer buyer 5% reduction from cart total if they select local pickup as a shipping method.
I already tried - 5 * [qty] and it doesn't seem to be working.
I also tried -0.95 * [cost] with no luck
I am using WooCommerce 3 and achieved the above result by writing a function inside the function.php of active theme.
function prefix_add_discount_line( $cart ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping_no_ajax = $chosen_methods[0];
if ( 0 === strpos( $chosen_shipping_no_ajax, 'local_pickup' ) ) {
// Define the discount percentage
$discount = $cart->subtotal * 0.05;
// Add your discount note to cart
$cart->add_fee( __( 'Collection discount applied', 'yourtext-domain' ) , -$discount );
}
}
add_action( 'woocommerce_cart_calculate_fees', 'prefix_add_discount_line');
The problem with the fee API is that it always apply Taxes for negative fee (Discount) and don't care about existing coupons discounts.
The code below, will set a defined discount percentage In the shipping method "Local pickup" itself.
You will need to set a reference shipping cost with a simple initial cost instead of your formula. It can be for example 10, and will be replaced by the code discount.
You may have to "Enable debug mode" in general shipping settings under "Shipping options" tab, to disable temporarily shipping caches.
The code (where you will set your discount percentage):
add_filter('woocommerce_package_rates', 'local_pickup_percentage_discount', 12, 2);
function local_pickup_percentage_discount( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// HERE define the discount percentage
$percentage = 5; // 5%
$subtotal = WC()->cart->get_subtotal();
// Loop through the shipping taxes array
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
// Targetting "flat rate"
if( 'local_pickup' === $rate->method_id ){
// Add the Percentage to the label name (otional
$rates[$rate_key]->label .= ' ( - ' . $percentage . '% )';
// Get the initial cost
$initial_cost = $new_cost = $rates[$rate_key]->cost;
// Calculate new cost
$new_cost = -$subtotal * $percentage / 100;
// 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;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Don't forget to disable "Enable debug mode" option in shipping settings.
I want to count shipping cost based on number of products add on cart like,
If I purchase one mobile then it will count shipping cost as 2.5 and after more than two or two mobile I purchased then shipping cost will be 5.0
<?php
$qty(1) * 2.5 = 2.5
$qty(2) * 2.5 = 5.0
$qty(3) * 2.5 = 5.0
?>
So is there any idea or suggestion how to count the shipping cost based on number of products ?
Updated:
As your question is a bit unclear, you could just need to add [qty]*2.5 in the Flat rate shipping method cost (for each shipping zone) in your wooCommerce shipping settings.
But it will not work if you have 2 different items in cart like: item1 (qty 1) + item2 (qty 1)
So this answer will do it in all cases:
1) First you will need to set a "Flat rate" shipping method for each Shipping Zones which cost will be set to 2.5 (in your WooCommerce shipping settings).
2) Adding this code that will calculate for each cart items (based on the total quantity of items) the new updated shipping cost:
add_filter( 'woocommerce_package_rates', 'custom_flat_rate_cost_calculation', 10, 2 );
function custom_flat_rate_cost_calculation( $rates, $package )
{
// The cart count (total items in cart)
$cart_count = WC()->cart->get_cart_contents_count();
$taxes = array();
// If there is more than 1 cart item
if( $cart_count > 1 ){
// Iterating through each shipping rate
foreach($rates as $rate_key => $rate_values){
// Targeting "Flat Rate" shipping method
if ( 'flat_rate' === $rate_values->method_id ) {
// Set the new calculated rate cost
$rates[$rate_id]->cost = number_format($rates[$rate_id]->cost * $cumulated_active_quantity, 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] * $cumulated_active_quantity, 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.
This code is tested on WooCommerce 3+ and works
You will need to refresh shipping zones caches: disabling the Flat rate, then save. And enabling back this rate and save.
You can add a custom fee: Add to theme functions.php or use a plugin
add_action( 'woocommerce_cart_calculate_fees','woocommerce_custom_surcharge' );
function woocommerce_custom_surcharge() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$price_per_mobile = 2.5;
$shipcharge = ( $woocommerce->cart->cart_contents_total * $price_per_mobile);
$woocommerce->cart->add_fee( 'Total Shipping Cost', $shipcharge, true, '' );
}
Go ahead with Woocommerce filter woocommerce_package_rates. Where you can customize all the shipping rates available in cart page.
Here is the code for adding extra cost to all items for both domestic and international shipement
add_filter('woocommerce_package_rates', 'wf_modify_rate', 10, 3);
function wf_modify_rate( $available_shipping_methods, $package ){
$origin_country = 'US';
$amount_to_add_domestic = 10;
$amount_to_add_inter_national = 20;
$amount_to_add = ($package['destination']['country'] == $origin_country) ?
$amount_to_add_domestic : $amount_to_add_inter_national;
$item_count = 0;
foreach ($package['contents'] as $key => $item) {
$item_count += $item['quantity'];
}
foreach ($available_shipping_methods as $methord_name => $methord) {
$available_shipping_methods[$methord_name]->cost += ($amount_to_add*$item_count);
}
return $available_shipping_methods;
}