Increase WooCommerce tax calculations precision and keep displayed prices with two decimals - php

In Woocommerce settings, I have set 6 decimals in order to get more accurate tax calculation. However, I need all prices and amounts to be displayed with only 2 decimals in frontend, emails etc. I found two functions
add_filter('wc_price_args', 'custom_decimals_price_args', 10, 1);
function custom_decimals_price_args($args) {
$args['decimals'] = 2;
return $args;
}
add_filter( 'wc_get_price_decimals', 'change_prices_decimals', 20, 1 );
function change_prices_decimals( $decimals ){
$decimals = 2;
return $decimals;
}
What is the difference between these and which one should I use?

Note that WC_ROUNDING_PRECISION constant is set to 6 in WC_Woocommerce define_constants() method.
That means that WooCommerce Tax calculation precision is already set on 6 decimals.
Tax calculation precision are based on wc_get_rounding_precision() core function used in WC_Tax Class:
function wc_get_rounding_precision() {
$precision = wc_get_price_decimals() + 2;
if ( absint( WC_ROUNDING_PRECISION ) > $precision ) {
$precision = absint( WC_ROUNDING_PRECISION );
}
return $precision;
}
As you can see WC_ROUNDING_PRECISION constant is prioritized if the displayed price decimal value + 2 is smaller than WC_ROUNDING_PRECISION constant. But as you want to keep displayed prices with 2 decimals, this requires something else.
So you should not increase displayed price decimals and not use wc_price_args or/and wc_get_price_decimals hooks, to increase precision in tax calculation.
If precision of 6 decimals is not enough and you want to get more precision:
How to get more precision on Tax calculations?
The best way to get more precision on tax calculations and keep displayed prices with 2 decimals is to edit WordPress wp_config.php file and add the following lines (where you can increase WC_ROUNDING_PRECISION constant value as you like, here the value is set to 8 for example):
// Change WooCommerce rounding precision
define('WC_ROUNDING_PRECISION', 8);
This will change WC_ROUNDING_PRECISION constant without affecting displayed price decimals.

wc_price_args is used to add extra arguments to the price - it takes in raw price and it can add other stuff to it like for example a currency symbol. When get_price is called it will include this currency symbol in the front end.
https://wp-kama.com/plugin/woocommerce/hook/wc_price_args
wc_get_price_decimals it sets a number of decimals after the 'point' - eg. if you set it to 5, the price will look like 0.00000
https://wp-kama.com/plugin/woocommerce/function/wc_get_price_decimals
I didn't test this but it should do the job.
add_filter('wc_price_args', 'custom_decimals_price_args', 10, 1);
function custom_decimals_price_args($price, $args) {
return number_format((float)$price, 2, '.', '');
}
Alternatively, you can edit the template where your products are and format the get_price with the number_format above.
Let me know if this worked for you.

Related

How to keep three decimals for prices, while showing two decimals on the frontend

i have a Variable Product with a very small price (0.023€).
As shown here: https://imgur.com/a/iwkc2B7
I have set the minimum quantity amount to 1000.
The correct price when someone selects a quantity of 1000 should be:
0.023€ * 1000 = 23€
But my Currency Switcher (Aelia) calculates it wrong. Because i have set the decimals to 2, it only takes the first 2 decimals into the calculation.
It displays 20€ instead of 23€. (0.02 * 1000 = 20€)
Only if i change the decimals in the settings from 2 to 3. It calculates it correctly and displays the correct price (23€)
But unfortunately, it then looks like this: 23.000€
https://imgur.com/a/SUoEoiv
With 3 decimals it shows the correct price, but i dont want it to have 3 decimals displayed. I want to display only 2 decimals, like this: 23.00€
I already tried a couple of filters, but none seem to work for me:
//Test 1
add_filter('wc_price_args', function($args) {
// Always format prices with two decinmals
$args['decimals'] = 2;
return $args;
}, 10);
//Test 2
add_filter('wc_price_args', 'custom_decimals_price_args', 10, 1);
function custom_decimals_price_args($price, $args) {
return number_format((float)$price, 2, '.', '');
}
//Test 3
add_filter( 'wc_get_price_decimals', 'change_prices_decimals', 20, 1 );
function change_prices_decimals( $decimals ){
$decimals = 2;
return $decimals;
}
I also tried this solution, that the currency switcher plugin provides for rounding Taxes, but doesnt work either:
https://aelia.freshdesk.com/support/solutions/articles/3000038344-aelia-currency-switcher-how-to-implement-custom-rounding-of-converted-prices
Any ideas how i could fix this?
Thank you in advance
You can add conditions for frontend / backend with the function is_admin() like so:
add_filter('wc_price_args', 'BN_filter_price_decimals');
function BN_filter_price_decimals($args) {
//Backend = 3
if (is_admin()) { $args['decimals'] = 3;
}
// Frontend = 2
else { $args['decimals'] = 2; }
}
return $args;
}

PHP: Calculating percentage tax applied to a product

I've got a significant problem in working out the percentage tax applied to a product, due to the rounding.
For example:
If you have a product which is £1.00 including 20% tax the break down would be:
0.83 ex tax
0.17 tax
1.00 total
However, if you work out the percentage increase:
round( (( ( 1 - 0.83 ) / 0.83 ) * 100), 2);
The answer is 20.48, because the actual price ex VAT is 0.8333333333
Therefore if you calculate:
round( (( ( 1 - 0.8333333333 ) /0.8333333333 ) * 100), 2);
You get the correct answer of 20.
In this case it would obviously work to round the 20.48 down to 20, but thats not a solution because some tax rates are to 2 decimal places so the assumption can't be made that you can just round the tax rate.
Am I missing something or is this impossible without knowing the original tax percentage?
0.17 is not 20% of 0.83, so your basic assumption is inaccurate( is rounded :P ).
Don't round money, calculate it without all that rounding and display rounded if need be. That avoids having to loose the precision in calculations.
A simple test will demonstrate
$price=0.8333333333;
$taxRate=21.25;
$tax=$price*$taxRate/100;
$total=$price+$tax;
$calculatedTaxRate=(($total-$price)/$price)*100; // 21.25
Since we didn't round anywhere, we can reverse engineer the tax rate always down to the dot.
Try with 20%
$price=0.8333333333;
$taxRate=20;
$tax=$price*$taxRate/100;
$total=$price+$tax;
$calculatedTaxRate=(($total-$price)/$price)*100; // 20
Wouldn't it something like:
17% VAT
85.47 taxless
85.47x0.17 = 14.53
Total: 100
So 100/1.17 = 85.47
My little-and-dirty version:
$taxrate=22; // 22%
$price=100;
$priceMinusTax=($price/(($taxrate/100)+1));
As long as you are dealing with low precision numbers, you will get a low precision answer. If you must show low precision numbers, you can round them when you show them to the user in the view, but save them as high precision numbers in the database.
Assuming your total price is £1 and tax rate is 20%:
$vatDecimal = (float) 1 + (20 / 100);
$priceExclVAT = round((1.00 / $vatDecimal), 2);
$priceDisplay = number_format($priceExclVAT, 2, ',', '.');
echo $priceDisplay;
The user enters the total amount and tax. Sometime its come form database, we can also use this code.
$actualPrice = "";
$total = 1000;//User Entry Total Amount
$gst = 18;//User Entry GST Tax %
$calculateTax = 100+$gst;
$calculateAmount = $total*100;
$actualPrice = $calculateAmount/$calculateTax;
echo $actualPrice = round($actualPrice,2);
echo $gstAmount = round($total-$actualPrice,2);

How to calculate with php the total with a VAT percentage in PHP

Im trying to make a calculation with the following values:
Product cost (without VAT) = 12,40 ($product)
The VAT percentage = 21%, what I will store in the database as 0,21 ($vat_perc)
The VAT is 2,604 ($vat)
edit: The VAT is per product
When I try to get the total then I get 15,00 ($total)
What I did is the following:
$total = $product + $vat
This will echo 15.004
Then I use the number_format:
echo(number_format($total,2,',','.'));
This will print 15.00
But then I want to multiply the product with 2
So that will give the following calculation:
$total = $product * 2 + $vat
Then again I use the format:
echo(number_format($total,2,',','.'));
Then the total = 30,01
I tried serveral things like ROUND en INT, but with no succes.
What am I doing wrong in this? In know that the VAT is the evil one here but I have no idea how to fix this.
$tax = round( ($price / 100) * 3.8, 2);
tax is rounded price divided by the 100 to make a clear percentage. Multiplied by the wanted stack
then you do the addition to or from your price table.
Well good to have you on the phone - maybe we can solve this faster by phone. Thank god for phones!
Cheers mate!
Here are some examples of how the numbers are rounded with PHP functions.
$product = 12.40;
$vat = 2.644;
$total = ( $product + $vat ) * 2;
var_dump( $total ); // float(30.088)
var_dump( number_format($total,2,',','.') ); // string(5) "30,09", rounded
var_dump( round( $total, 2 ) ); // float(30.09), rounded
var_dump( sprintf('%0.2f', $total ) ); // string(5) "30.09", rounded
var_dump( floor( $total * 100 ) / 100 ); // float(30.08), not rounded
All three founctions ( number_format, round, sprintf ) will round the result, to avoid the rounding and discard the decimal part after two decimal points you can use the last example.
Your initial total is 15.004 so when you call number_format that gets rounded down to 15.00. Now when you multiply by 2 your total is 15.008 which number_format will round up to 15.01. The issue isn't with the addition it is with the multiplication by 2. number_format rounds to the nearest place which for your case would be 30.01.
If you want the number to be rounded down all the time use floor, like so:
$total = floor(($product * 200)) / 100 + $vat;
echo(number_format($total,2,',','.'));

PHP money calculation precision

I have a project that stores money as bigint column in a database (storing in cents). I'm planning to rewrite this thing to use BCMATH instead. I don't mind integers, but they give me some terrible rounding errors event stored in cents and I suspect I might have the same rounding errors in BCMATH. The problem arises in situations, like in this pseudocode:
$price = $some_price_in_cents * $store_price_increase; // second value is a float for final price calculation, so the result might have fractions of a cent
$price_total = $price * $qty;
$discount = // some discount in cents (might have fractions of a cent)
$discount *= $qty;
$discounted_price = $price_total - $discount;
When inserting into a database, I do round() on all values in cents. And now I have a record which says:
total price = 12134
discount = 460
discounted price = 11675
Now if I do 12134 - 460 ... I obviously get 11674 and not 11675.
I also suspect that if I changed the way things are calculated (eg. multiply everything by the QTY at the end), I'd get even different results.
Would I get this kind of behaviour using BCMATH? Would the result depend on the order of math operations? How would I properly calculate the above using BCMATH and store it in DB (assuming 2 decimal places are required)?
I believe this is what you need. Note that bcmath requires strings. The number 2 is to specify how many decimals you need.
$price = bcmul($some_price_in_cents, $store_price_increase, 2);
$price_total = bcmul($price, $qty, 2);
$discount = bcmul($qty, "discount amount", 2);
$discounted_price = bcsub($price_total, $discount, 2);

Generate a fictitious stock option price variation

What I do
I am making graph of fictitious stock options.
The price is updated each second, with this function
function stockVariation($price,$max_up,$max_down)
{
// Price > 1
if($price > 1)
{
// Calculate
$ratio=(mt_rand(0,$max_up/2)-mt_rand(0,$max_down/2))/1000;
$price+=$ratio;
}
// Price <=1 (we don't want 0 or negative price...)
else
$price+=mt_rand(1,$max_up)/1000;
return round($price,3);
}
I use a max_up and max_down values (from 10 to 100) to make the price change progressively and simulate some volatility.
For example, with max_up : 40 and max_down : 45, the price will progressively go down.
My question
But the problem, is that prices generated are too much volatile, even if max_up = max_down.
The result is "non-natural". (for example +10 points in one day for a base price of 15,000).
Result of price evolution per hour in 24 hour
Perhaps making round($price,4) and divisions by 10 000 instead of 1 000, will be better ?
If anyone have an idea or an advice to generate "natural" prices evolution, thanks in advance.
There are 86400 seconds in a day, so you'll need to divide by a much larger number. And rather than adding and subtracting, you may want to multiply the current price by a factor that's slightly larger or smaller than 1. That would simulate a percentage increase or decrease, rather than an absolute gain or loss.
function stockVariation($price, $max_up, $max_down)
{
// Convert up/down to fractions of the current price.
// These will be very small positive numbers.
$random_up = mt_rand(0, $max_up) / $price;
$random_down = mt_rand(0, $max_down) / $price;
// Increase the price based on $max_up and decrease based on $max_down.
// This calculates the daily change that would result, which is slightly
// larger or smaller than 1.
$daily_change = (1 + $random_up) / (1 + $random_down);
// Since we're calling this function every second, we need to convert
// from change-per-day to change-per-second. This will make only a
// tiny change to $price.
$price = $price * $daily_change / 86400;
return round($price, 3);
}
Building upon the idea, you could use an actual volatility number. If you want e.g. a volatility of 35%/year, you can find the volatility per second. In pseudocode:
vol_yr = 0.35
vol_month = vol_yr * sqrt(1.0/12)
vol_second = vol_yr * sqrt(1.0/(252 * 86400)) # or 365 * 86400
Then, every second, you "flip a coin" and either multiply or divide current stock price by (1 + vol_second). This is the principle of how binomial trees are created to evaluate exotic stock options.

Categories