I'm building a Woocommerce store and I need to get all the prices with tax included to end in zero (For example, if the original price with tax included is 1234, then I round it to 1240).
Products are imported to my website in bulk (+2800 products) but they have the price without tax. I configured the tax in woocommerce settings and I display prices with tax included in the store.
To fix the "ending in 0" problem, I created a method to change the tax value so the final price ends in 0:
// Calculate tax
function filter_woocommerce_calc_tax( $taxes, $price, $rates, $price_includes_tax, $suppress_rounding ) {
$key = array_search('IVA', array_column_keys($rates, 'label')); // find tax with label "IVA"
$tax = $taxes[$key]; // get the current tax
$subtotal = $price + $tax; // get the total
$final = ceil($subtotal / 10) * 10; // modify the total so it ends in 0
$new_tax = $final - $price; // calculate new tax price
$taxes[$key] = $new_tax; // update tax in array
return $taxes; // return new calculated taxes
};
add_filter( 'woocommerce_calc_tax', 'filter_woocommerce_calc_tax', 10, 5 );
It works really good, but I figured that if I change the tax price, I'm actually changing the tax rate (%) and I can't do that for legal reasons.
That's why I wanted to change the price of the product without tax instead.
I can use the same method:
$final = ceil($subtotal / 10) * 10; // modify the total so it ends in 0
$new_price = $final - $tax; // new price without tax
But I don't know what hook to use to achieve this.
Is is possible to change the price with hooks and filters?
I finally got a solution, first I created a method to calculate the prices without tax:
function get_net_sales_price($price, $tax_rate = 0) {
$subtotal = $price + (1+$tax_rate);
$final = ceil($subtotal / 10) * 10;
$new_price = $final / (1+$tax_rate);
return $new_price;
}
Then I got the taxes based on the product and customer location (thanks to this answer):
function get_tax_rates($product) {
// Get an instance of the WC_Tax object
$tax_obj = new WC_Tax();
// Get the tax data from customer location and product tax class
return $tax_obj->find_rates(array(
'country' => WC()->customer->get_shipping_country() ? WC()->customer->get_shipping_country() : WC()->customer->get_billing_country(),
'state' => WC()->customer->get_shipping_state() ? WC()->customer->get_shipping_state() : WC()->customer->get_billing_state(),
'city' => WC()->customer->get_shipping_city() ? WC()->customer->get_shipping_city() : WC()->customer->get_billing_city(),
'postcode' => WC()->customer->get_shipping_city() ? WC()->customer->get_shipping_city() : WC()->customer->get_billing_city(),
'tax_class' => $product->get_tax_class()
));
}
And finally I use a hook to return the correct price for the product (woocommerce then adds the tax when needed):
function filter_woocommerce_price($price, $product) {
$rates = get_tax_rates($product);
// Finally we get the tax rate and calculare net sales price:
if( ! empty($rates) ) {
$key = array_search('IVA', array_column_keys($rates, 'label'));
return get_net_sales_price($price, $rates[$key]['rate'] / 100);
} else {
return get_net_sales_price($price);
}
}
add_filter('woocommerce_product_get_price', 'filter_woocommerce_price', 99, 2 );
add_filter('woocommerce_product_get_regular_price', 'filter_woocommerce_price', 99, 2 );
add_filter('woocommerce_product_variation_get_regular_price', 'filter_woocommerce_price', 99, 2 );
add_filter('woocommerce_product_variation_get_price', 'filter_woocommerce_price', 99, 2 );
Now I'm getting the right tax amount, and also all the product prices end in 0.
Related
The code is supposed to check if the fixed coupon amount is bigger than cart subtotals. If yes, subtract the remainder of the price from shipping totals. It's in my functions file.
add_filter('woocommerce_package_rates', 'custom_shipping_costs', 10, 2 );
function custom_shipping_costs( $rates, $package ){
//get the shipping total, cart subtotal and coupon amount
$dostava = $cart->shipping_total;
$iznos = $woocommerce->cart->get_cart_subtotal();
$kuponi = $woocommerce->cart->discount_total;
//subtract the coupon amount from cart subtotal
$razlika = $iznos - $kuponi;
//now check if it's higher than what's in the cart, if yes, set the new shipping costs
if($razlika < 0){
$novadostava = $dostava - $razlika;
$cart->shipping_total = $novadostava;
}
return $rates;
}
Unfortunately.. it doesn't work. Any suggestions?
Here is an example since there are more to consider to it as taxes rates shipping methods if must define etc but current example works as it
We have product that cost 18$ and coupon that discount 20$ with shipping tax 15$ at the end we get 13$ total - https://prnt.sc/365xV2BWx2wA
add_filter('woocommerce_package_rates', 'custom_shipping_costs');
function custom_shipping_costs( $rates ){
$cart = WC()->cart;
if($rates):
foreach($rates as $rate_key => $rate):
$dostava = $rate->get_cost();
endforeach;
endif;
$iznos = $cart->subtotal;
$kuponi = $cart->get_coupons();
if($kuponi):
foreach($kuponi as $kupon):
$kuponi_amount = $kupon->get_amount();
endforeach;
endif;
$razlika = $iznos - $kuponi_amount;
$razlika = abs($razlika);
if($razlika > 0){
$novadostava = $dostava - $razlika;
$rates[$rate_key]->cost = $novadostava;
}
return $rates;
}
I want to export only the coupon totals from items with reduced tax rate (the totals you can see in the image below).
With the totals of these items I do the same. A great answer helped me to export only the totals of order items with reduced tax rate. For that I'm using the following code:
// gets the total of the order items by tax class
function get_total_order_items_by_tax_class( $order_id, $tax_class = 'reduced-rate' ) {
$order = wc_get_order( $order_id );
// initializes the total of the order items
$total = 0;
foreach( $order->get_items() as $item_id => $order_item ) {
// if the product tax class is equal to "$tax_class"
if ( $tax_class == $order_item['tax_class'] ) {
// sum the total
$total += $order_item['total'];
}
}
return $total;
}
I tried something similar and added the line (found here):
$order->get_discount_total();
to the snippet. But this exports the whole discount for every item of every tax class.
I also tried the following code from this answer:
foreach( $order->get_coupon_codes() as $coupon_code ) {
// Get the WC_Coupon object
$coupon = new WC_Coupon($coupon_code);
$discount_type = $coupon->get_discount_type(); // Get coupon discount type
$coupon_amount = $coupon->get_amount(); // Get coupon amount
}
But it's also the discount for the whole order.
Is there any way to get the discount totals only for items with reduced tax rates?
I believe that there must be a way because the order shows these totals below every item.
But I couldn't find a way to get these discounts.
I saw that $order only contains the coupon total and the coupon tax. Not per line item. And it seems that $order_item doesn't contain any discounts. Only something line WC_Coupon_Data_Store_CPT.
Try the following to get order items discount amount by tax class:
// Get order items discount amount excl. taxes by tax class
function get_order_items_discount_total_by_tax_class( $order_id, $tax_class = 'reduced-rate' ) {
$order = wc_get_order( $order_id );
$discount = 0; // Initializing;
foreach( $order->get_items() as $item ) {
// if the product tax class is equal to "$tax_class"
if ( $tax_class == $item['tax_class'] ) {
$discount += $item->get_subtotal() - $item->get_total(); // Excluding taxes
}
}
return $discount;
}
It should work.
Related: Get Order items and WC_Order_Item_Product in WooCommerce 3
I have setup taxes inclusive for all my items and filled MRP in price. But now I want to apply tax inclusive if customer didn't applied coupon i.e. buying on MRP. But when customer applies coupon I need to apply taxes on after discount amount.
Is it possible with settings within Woocommerce or is there any plugin available?
For e.g.
**Case I**
Product MRP = 670
Shipping = 50
Tax 18% = 102
Final price = 670 (Including Taxes)
It's Fine.
**Case II**
Product MRP = 670
Discount 40%= 268
Price = 402
Shipping = 50
Tax 18% = 61
Final price = 452 (Including Taxes)
But I need tax to calculated exclusively on discounted price i.e. 402+18% = 474+50 (Ship) = 524
I have tried following filter in my custom plugin:
add_filter( 'woocommerce_calc_tax', 'inc_or_exc',10,3 );
// add_filter( 'woocommerce_calculate_totals', 'calculate_totals',11 );
function inc_or_exc( $taxes,$price,$rates ) {
// echo "<pre>";
if(!empty(WC()->cart->coupon_discount_amounts)){
return WC_Tax::calc_exclusive_tax( $price, $rates );
}else{
return WC_Tax::calc_inclusive_tax( $price, $rates );
}
}
But it calculates taxes bit strange. If item MRP is 100, it shows 98.85 and also totals are not updating with new taxes and shipping rates after plugin run. If I disable plugin then item MRP is shown fine i.e. 100.
Finally I have solved it.
First I applied inclusive exlusive filter. Then called woocommerce_calculated_total with custom condition and achieved my motive.
add_filter( 'woocommerce_calc_tax', 'inc_or_exc',10,3 );
// do_action('add_points');
add_filter( 'woocommerce_calculated_total', 'custom_calculated_total', 10, 2 );
function inc_or_exc( $taxes,$price,$rates ) {
// echo "<pre>";
if(!empty(WC()->cart->coupon_discount_amounts)){
return WC_Tax::calc_exclusive_tax( $price, $rates );
}else{
return WC_Tax::calc_inclusive_tax( $price, $rates );
}
}
function custom_calculated_total( $total, $cart ){
// echo "<pre>";
if(!empty(WC()->cart->coupon_discount_amounts)){
return round( $total + WC()->cart->get_cart_contents_tax(), $cart->dp );
}else{
return round( $total, $cart->dp );
}
}
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 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;
});