Shipping cost discount based on a shipping classes in Woocommerce - php

I'm trying to apply a discount to one shipping class for products currently in a cart. This is applied on the checkout view.
In Woocommerce backend, the option is set to charge each shipping class individually. Also, I use only one shipping method named "flat rate".
Based on Override all shipping costs for a specific shipping class in Woocommerce, the following code that should apply the discount:
add_filter('woocommerce_package_rates', 'shipping_class_null_shipping_costs', 10, 2);
function shipping_class_null_shipping_costs( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
$shipping_class_slug = 'large'; // Your shipping class slug
$found = false;
// Loop through cart items and checking for the specific defined shipping class
foreach( $package['contents'] as $cart_item ) {
if( $cart_item['data']->get_shipping_class() == $shipping_class_slug )
$found = true;
}
$percentage = 50; // 50%
$subtotal = WC()->cart->get_cart_shipping_total();
// Set shipping costs to 50% discount if shipping class is found
if( $found ){
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
// Targetting "flat rate"
if( 'flat_rate' === $rate->method_id ){
$rates[$rate_key]->cost = $subtotal;
}
}
}
return $rates;
}
But whatever I try, the calculated shipping result is $0.
What am I doing wrong here and what would be the correct way to apply a discount to shipping class?
Thank you.

Update (Just about settings)
To add a discount only for "large" shipping class on "Flat rate" shipping method, You will have to:
Set the discounted price directly on your shipping method cost.
Enable option "Per class: Charge shipping for each shipping class individually"
Like:
Original answer:
The following code will set the shipping cost of 50% for "Flat rate" shipping method, when a specific defined shipping method is found in cart items.
Testing: Temporary "Enable debug mode" in Shipping settings under Shipping options tab...
Shipping "Flat rate" settings: Your shipping classes costs should be defined.
In the code below define in each function your shipping class slug and your custom notice:
add_filter('woocommerce_package_rates', 'shipping_costs_discounted_based_on_shipping_class', 10, 2);
function shipping_costs_discounted_based_on_shipping_class( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// Your settings below
$shipping_class = 'large'; // <=== Shipping class slug
$percentage = 50; // <=== Discount percentage
$discount_rate = $percentage / 100;
$is_found = false;
// Loop through cart items and checking for the specific defined shipping class
foreach( $package['contents'] as $cart_item ) {
if( $cart_item['data']->get_shipping_class() == $shipping_class )
$is_found = true;
}
// Set shipping costs to 50% if shipping class is found
if( $is_found ){
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
// Targeting "flat rate"
if( 'flat_rate' === $rate->method_id ){
$rates[$rate_key]->cost = $rate->cost * $discount_rate;
// Taxes rate cost (if enabled)
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $tax > 0 ){
$has_taxes = true;
$taxes[$key] = $tax * $discount_rate;
}
}
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 "debug mode" in shipping settings once this has been tested once.

Related

Shipping rate applied only if a certain coupon is used in WooCommerce

I need to create 2000 coupons to sell, but I would like the customers who will use them to always pay for shipping. Currently the threshold for getting free shipping is set above 69€. I tried to use the code below (taken from here: Applied coupons disable Free shipping conditionally in Woocommerce).
It applies to all coupons though, and I'd like to apply it only on coupons with the prefix 'pd'.
add_filter( 'woocommerce_package_rates', 'coupons_removes_free_shipping', 10, 2 );
function coupons_removes_free_shipping( $rates, $package ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
$applied_coupons = WC()->cart->get_applied_coupons();
if( sizeof($applied_coupons) > 0 ) {
// Loop through shipping rates
foreach ( $rates as $rate_key => $rate ) {
// Targeting "Free shipping" only
if( 'free_shipping' === $rate->method_id ) {
unset($rates[$rate_key]); // Removing current method
}
}
}
return $rates;
}

Keep highest flat rate shipping cost and local pickup in Woocommerce

I'm trying to show the highest shipping costs in the cart. I've found a nice little snippet for that:
function only_show_most_expensive_shipping_rate( $rates, $package ) {
$most_expensive_method = '';
$new_rates = array();
// Loop through shipping rates
if ( is_array( $rates ) ) {
foreach ( $rates as $key => $rate ) {
// Set variables when the rate is more expensive than the one saved
if ( empty( $most_expensive_method ) || $rate->cost > $most_expensive_method->cost ){
$most_expensive_method = $rate;
}
}
}
// Return the most expensive rate when possible
if ( ! empty( $most_expensive_method ) ){
/**
** Keep local pickup if it's present.
**/
foreach ( $rates as $rate_id => $rate ) {
if ('local_pickup' === $rate->method_id ) {
$new_rates[ $rate_id ] = $rate;
break;
}
}
return array( $most_expensive_method->id => $most_expensive_method );
}
return $rates;
}
add_action('woocommerce_package_rates', 'only_show_most_expensive_shipping_rate', 10, 2);
However this snippet also hides the "local pickup" shipping method.
Why does the above method doesn't work? Right now it only shows the highest shipping class/price and hide all others including the pickup method.
Is it because of the two arrays? I don't see any errors popping up.
Any help greatly appreciated!
The following will keep the highest shipping Flat rate cost and the Local pickup shipping method:
add_action('woocommerce_package_rates', 'keep_highest_flat_rate_cost', 10, 2);
function keep_highest_flat_rate_cost( $rates, $package ) {
$flat_rate_costs = [];
// Loop through shipping methods rates
foreach ( $rates as $key_rate => $rate ) {
// Targeting only "Flat rate" type shipping methods
if ( ! in_array( $rate->method_id, ['local_pickup', 'free_shipping'] ) ) {
// Store the Rate ID keys with corresponding costs in an indexed array
$flat_rate_costs[$key_rate] = $rate->cost;
}
}
// Sorting "Flat rate" costs in DESC order
arsort($flat_rate_costs);
// Remove the highest cost from the array
array_shift($flat_rate_costs);
// Loop through remaining "Flat rate" shipping methods to remove them all
foreach ( $flat_rate_costs as $key_rate => $cost){
unset($rates[$key_rate]);
}
return $rates;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
You should need to refresh the shipping caches:
1) First ensure that the code is already saved on your function.php file.
2) In Shipping settings, enter in a Shipping Zone: Disable any Shipping Method and "save", then re-enable it and "save". You are done.

Override all shipping costs for a specific shipping class in Woocommerce

I have a number of shipping classes with prices set in multiple shipping zones. Is it possible to detect if ANY product within the cart belongs to a specific shipping class, and if so set the shipping cost to 0 and display a message?
I would like to use this mechanism for products which require special handling and would be shipped via freight. So if a customer's order contains any of these products the shipping quote would be provided manually post-sale.
Thanks for the help.
The following code will set all shipping cost to zero, when a specific defined shipping method is found in cart items and will display a custom notice.
The second hooked function is optional and will display the custom notice in checkout page.
You will have to temporarily "Enable debug mode" in Shipping settings under Shipping options (tab) to disable / clear caching.
In the code below define in each function your shipping class slug and your custom notice:
// Null shipping costs for a specific shipping class and display a message
add_filter('woocommerce_package_rates', 'shipping_class_null_shipping_costs', 10, 2);
function shipping_class_null_shipping_costs( $rates, $package ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return $rates;
// HERE set your shipping class slug
$shipping_class_slug = 'extra';
$found = false;
// Loop through cart items and checking for the specific defined shipping class
foreach( $package['contents'] as $cart_item ) {
if( $cart_item['data']->get_shipping_class() == $shipping_class_slug )
$found = true;
}
// Set shipping costs to zero if shipping class is found
if( $found ){
foreach ( $rates as $rate_key => $rate ){
$has_taxes = false;
// Targetting "flat rate" and local pickup
if( 'flat_rate' === $rate->method_id || 'local_pickup' === $rate->method_id ){
// Set the cost to zero
$rates[$rate_key]->cost = 0;
// Taxes rate cost (if enabled)
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 ){
$has_taxes = true;
// Set taxes cost to zero
$taxes[$key] = 0;
}
}
if( $has_taxes )
$rates[$rate_key]->taxes = $taxes;
}
}
// Clear duplicated notices
wc_clear_notices();
// Add a custom notice to be displayed
wc_add_notice( __('My custom shipping message here.', 'woocommerce'), 'notice' );
}
return $rates;
}
add_action( 'woocommerce_before_checkout_form', 'display_custom_shipping_message_in_checkout' );
function display_custom_shipping_message_in_checkout(){
// HERE set your shipping class slug
$shipping_class_slug = 'extra';
$found = false;
// Loop through cart items and checking for the specific defined shipping class
foreach( WC()->cart->get_cart() as $cart_item ) {
if( $cart_item['data']->get_shipping_class() == $shipping_class_slug )
$found = true;
}
if( $found ){
// Display a custom notice
wc_print_notice( __('My custom shipping message here.', 'woocommerce'), 'notice' );
}
}
Code goes in function.php file of your active child theme (or active theme). It should works.
Don't forget to disable "debug mode" in shipping settings once this has been tested once.
Set up a Shipping Class and Shipping Method for Free Shipping, assign that Class to the product you want to offer free shipping for, and then ensure that Free Shipping Method is set at the default shipping method.
Good Luck!

Filter Shipping method based on shipping class in Woocommerce 3

I have been searching for code to filter out any shipping methods other than local pick up, on checkout, when a product that has a specific shipping class selected (Only pickup, ex.) is in the cart (among other products).
I only found code that was outdated and that doesn't work on WC3+.
Here is the way to filter out any shipping methods other than local pick up, when a product that has a specific shipping class enabled:
add_filter( 'woocommerce_package_rates', 'custom_shipping_rates', 100, 2 );
function custom_shipping_rates( $rates, $package ) {
$shipping_class = 64; // HERE set the shipping class ID
$found = false;
// Loop through cart items Checking for the defined shipping method
foreach( $package['contents'] as $cart_item ) {
if ( $cart_item['data']->get_shipping_class_id() == $shipping_class ){
$found = true;
break;
}
}
if ( ! $found ) return $rates; // If not found we exit
// Loop through shipping methods
foreach( $rates as $rate_key => $rate ) {
// all other shipping methods other than "Local Pickup"
if ( 'local_pickup' !== $rate->method_id && $found ){
// Your code here
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works
Then in StackOverFlow searching for recent answer with woocommerce_package_rates will allow you to finish your code.

Custom dynamic shipping rate cost calculation in Woocommerce

I've set up a custom shipping method, i need to calculate shipping cost every time users get to the cart page.
It seems that woocommerce_package_rates action (where I calculate the custom shipping costs) runs only when user does click on a shipping method. This way the cart total is wrong most of the times, the worst is when the custom shipping method is already selected (user doesn't need to click it, so its cost doesn't update).
Is this the normal behavior of woocommerce_package_rates hook?
How to force woocommerce_package_rates to be always executed before displaying the cart totals?
EDIT
Here's some code i'm trying to hack with:
add_action( 'woocommerce_before_cart', 'force_shipping_calc', 1, 0 );
function force_shipping_calc() {
foreach( WC()->session->get('shipping_for_package_0')['rates'] as $rate_id =>$rate) {
// looking for the shipping method to recalc
if($rate->method_id == 'flat_rate') {
// mk1: set_shipping_total won't work, i'm using woocommerce < 3
//WC()->cart->set_shipping_total( MY_calculate_shipping() );
// mk2: doesn't work, "Indirect modification of overloaded property"
WC()->cart->totals['shipping_total'] = wc_format_decimal( MY_calculate_shipping(), wc_get_price_decimals() );
// mk3: cart total nor shipping total affected (?!)
WC()->cart->shipping_total = MY_calculate_shipping();
// mk4: ... ?! work in progress...
}
}
function MY_calculate_shipping() {
return 99.99;
}
add_filter( 'woocommerce_package_rates', 'fty_shipping_flat_rate_cost_calculation', 10, 2 );
function fty_shipping_flat_rate_cost_calculation($rates, $package) {
foreach($rates as $rate_key => $rate_values) {
$method_id = $rate_values->method_id;
$rate_id = $rate_values->id;
if ( 'flat_rate' === $method_id ){
$dist_cost = MY_calculate_shipping();
$price_excl_tax = $rates[$rate_id]->cost + $dist_cost;
$rates[$rate_id]->cost = number_format($price_excl_tax, 2);
$tax_keys = array_keys($rates[$rate_id]->taxes);
$price_excl_tax = $rates[$rate_id]->cost + $dist_cost;
$rates[$rate_id]->cost = number_format($price_excl_tax, 2);
$tax_calculation = $rates[$rate_id]->taxes[$tax_keys[0]] + $dist_cost*(TAX_AMOUNT_IVA-1);
$rates[$rate_id]->taxes[$tax_keys[0]] = number_format($tax_calculation, 2);
$rates[$rate_id]->cost += $dist_cost;
}
}
return $rates;
}
EDIT, again
This (mk. ~17786) seems to be in the right direction.
I changed the hook and force calculate_shipping() from WC_Shipping
add_action( 'woocommerce_cart_totals_before_shipping', 'fty_force_calculate_shipping', 1, 2550 );
function fty_force_calculate_shipping() {
WC()->shipping->calculate_shipping(WC()->shipping->packages);
WC()->cart->calculate_totals();
}
but it's not perfect yet, i think this hook makes a loop in the checkout page...
This needs to be done in woocommerce_package_rates only. In your code there is many errors or mistakes… Try the following:
function custom_calculated_shipping() {
return 99.99;
}
add_filter( 'woocommerce_package_rates', 'custom_shipping_rate_cost_calculation', 10, 2 );
function custom_shipping_rate_cost_calculation( $rates, $package ) {
foreach( $rates as $rate_key => $rate ) {
if ( 'flat_rate' === $rate->method_id ){
// Get rate cost and Custom cost
$initial_cost = $rates[$rate_key]->cost;
$additional_cost = custom_calculated_shipping();
// Calculation
$new_cost = $initial_cost + $additional_cost;
// Set Custom rate cost
$rates[$rate_key]->cost = round($new_cost, 2);
// Taxes rate cost (if enabled)
$new_taxes = array();
$has_taxes = false;
foreach ( $rate->taxes as $key => $tax ){
if( $tax > 0 ){
// Calculating the tax rate unit
$tax_rate = $tax / $initial_cost;
// Calculating the new tax cost
$new_tax_cost = $tax_rate * $new_cost;
// Save the calculated new tax rate cost in the array
$new_taxes[$key] = round( $new_tax_cost, 2 );
$has_taxes = true;
}
}
// Set new tax rates cost (if enabled)
if( $has_taxes )
$rate->taxes = $new_taxes;
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or theme). Tested and works. It will work in all woocommerce versions since 2.6…
You should need to refresh the shipping caches:
1) First this code is already saved on your function.php file.
2) Empty cart.
3) 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.

Categories