Merge pull request #26850 from woocommerce/fix/26654

Make sure shipping tax are not rounded down when prices are inclusive of taxes.
This commit is contained in:
Vedanshu Jain 2020-07-24 22:53:26 +05:30 committed by GitHub
commit dfe5493dd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 37 deletions

View File

@ -462,7 +462,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$tax_totals[ $code ]->is_compound = $tax->is_compound();
$tax_totals[ $code ]->label = $tax->get_label();
$tax_totals[ $code ]->amount += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total();
$tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
$tax_totals[ $code ]->formatted_amount = wc_price( $tax_totals[ $code ]->amount, array( 'currency' => $this->get_currency() ) );
}
if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
@ -672,7 +672,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
*/
protected function set_total_tax( $value ) {
// We round here because this is a total entry, as opposed to line items in other setters.
$this->set_prop( 'total_tax', wc_format_decimal( wc_round_tax_total( $value ) ) );
$this->set_prop( 'total_tax', wc_format_decimal( round( $value, wc_get_price_decimals() ) ) );
}
/**

View File

@ -6,6 +6,8 @@
*
* @var object $item The item being displayed
* @var int $item_id The id of the item being displayed
*
* @package WooCommerce/Admin/Views
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -86,7 +88,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<td class="line_tax" width="1%">
<div class="view">
<?php
echo wp_kses_post( ( '' !== $tax_item_total ) ? wc_price( wc_round_tax_total( $tax_item_total ), array( 'currency' => $order->get_currency() ) ) : '&ndash;' );
echo wp_kses_post( ( '' !== $tax_item_total ) ? wc_price( $tax_item_total, array( 'currency' => $order->get_currency() ) ) : '&ndash;' );
$refunded = $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'shipping' );
if ( $refunded ) {
echo wp_kses_post( '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );

View File

@ -341,7 +341,8 @@ final class WC_Cart_Totals {
$shipping_line->taxable = true;
$shipping_line->total = wc_add_number_precision_deep( $shipping_object->cost );
$shipping_line->taxes = wc_add_number_precision_deep( $shipping_object->taxes, false );
$shipping_line->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $shipping_line->taxes ) );
$shipping_line->taxes = array_map( array( $this, 'round_item_subtotal' ), $shipping_line->taxes );
$shipping_line->total_tax = array_sum( $shipping_line->taxes );
$this->shipping[ $key ] = $shipping_line;
}
@ -858,7 +859,7 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_totals() {
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + wc_round_tax_total( array_sum( $this->get_merged_taxes( true ) ), 0 ), 0 ) );
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ), 0 ) );
$this->cart->set_total_tax( array_sum( $this->get_merged_taxes( false ) ) );
// Allow plugins to hook and alter totals before final total is calculated.

View File

@ -849,6 +849,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return array
*/
public function get_tax_totals() {
$shipping_taxes = $this->get_shipping_taxes(); // Shipping taxes are rounded differently, so we will subtract from all taxes, then round and then add them back.
$taxes = $this->get_taxes();
$tax_totals = array();
@ -860,9 +861,17 @@ class WC_Cart extends WC_Legacy_Cart {
$tax_totals[ $code ] = new stdClass();
$tax_totals[ $code ]->amount = 0;
}
$tax_totals[ $code ]->tax_rate_id = $key;
$tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $key );
$tax_totals[ $code ]->label = WC_Tax::get_rate_label( $key );
if ( isset( $shipping_taxes[ $key ] ) ) {
$tax -= $shipping_taxes[ $key ];
$tax = wc_round_tax_total( $tax );
$tax += round( $shipping_taxes[ $key ], wc_get_price_decimals() );
unset( $shipping_taxes[ $key ] );
}
$tax_totals[ $code ]->amount += wc_round_tax_total( $tax );
$tax_totals[ $code ]->formatted_amount = wc_price( $tax_totals[ $code ]->amount );
}
@ -1902,7 +1911,7 @@ class WC_Cart extends WC_Legacy_Cart {
if ( ! $compound && WC_Tax::is_compound( $key ) ) {
continue;
}
$total += wc_round_tax_total( $tax );
$total += $tax;
}
if ( $display ) {
$total = wc_format_decimal( $total, wc_get_price_decimals() );

View File

@ -1,4 +1,9 @@
<?php
/**
* Helper class for shipping related unit tests.
*
* @package WooCommerce\Tests|Helper
*/
/**
* Class WC_Helper_Shipping.
@ -11,15 +16,17 @@ class WC_Helper_Shipping {
* Create a simple flat rate at the cost of 10.
*
* @since 2.3
*
* @param float $cost Optional. Cost of flat rate method.
*/
public static function create_simple_flat_rate() {
public static function create_simple_flat_rate( $cost = 10 ) {
$flat_rate_settings = array(
'enabled' => 'yes',
'title' => 'Flat rate',
'availability' => 'all',
'countries' => '',
'tax_status' => 'taxable',
'cost' => '10',
'cost' => $cost,
);
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
@ -28,6 +35,48 @@ class WC_Helper_Shipping {
WC()->shipping()->load_shipping_methods();
}
/**
* Helper function to set customer address so that shipping can be calculated.
*/
public static function force_customer_us_address() {
add_filter( 'woocommerce_customer_get_shipping_country', array( self::class, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( self::class, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( self::class, 'force_customer_us_postcode' ) );
}
/**
* Helper that can be hooked to a filter to force the customer's shipping state to be NY.
*
* @since 4.4.0
* @param string $state State code.
* @return string
*/
public static function force_customer_us_state( $state ) {
return 'NY';
}
/**
* Helper that can be hooked to a filter to force the customer's shipping country to be US.
*
* @since 4.4.0
* @param string $country Country code.
* @return string
*/
public static function force_customer_us_country( $country ) {
return 'US';
}
/**
* Helper that can be hooked to a filter to force the customer's shipping postal code to be 12345.
*
* @since 4.4.0
* @param string $postcode Postal code.
* @return string
*/
public static function force_customer_us_postcode( $postcode ) {
return '12345';
}
/**
* Delete the simple flat rate.
*

View File

@ -62,10 +62,8 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
'cost' => '9.59',
);
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
// Set an address so that shipping can be calculated.
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
WC()->cart->add_to_cart( $product->get_id(), 1 );
WC()->cart->add_discount( $coupon->get_code() );
@ -177,9 +175,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
// Set an address so that shipping can be calculated.
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
// Create tax classes first.
WC_Tax::create_tax_class( '23percent' );
@ -620,9 +616,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->empty_cart();
remove_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_gb_country' ) );
remove_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_gb_postcode' ) );
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
WC()->cart->add_to_cart( $product->get_id(), 1 );
// Test out of store location with no coupon.
@ -767,9 +761,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->empty_cart();
remove_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_gb_country' ) );
remove_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_gb_postcode' ) );
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
WC()->cart->add_to_cart( $product->get_id(), 1 );
// Test out of store location with no coupon.
@ -872,9 +864,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$full_coupon->set_amount( 100 );
$full_coupon->save();
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
WC()->cart->add_to_cart( $product->get_id(), 1 );
// Test out of store location with no coupon.
@ -941,7 +931,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
* @return string
*/
public function force_customer_us_country( $country ) {
return 'US';
return WC_Helper_Shipping::force_customer_us_country( $country );
}
/**
@ -952,7 +942,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
* @return string
*/
public function force_customer_us_state( $state ) {
return 'NY';
return WC_Helper_Shipping::force_customer_us_state( $state );
}
/**
@ -963,7 +953,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
* @return string
*/
public function force_customer_us_postcode( $postcode ) {
return '12345';
return WC_Helper_Shipping::force_customer_us_postcode( $postcode );
}
/**
@ -989,9 +979,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
update_option( 'woocommerce_calc_taxes', 'yes' );
// Set an address so that shipping can be calculated.
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
// 19% tax.
$tax_rate = array(
@ -1549,9 +1537,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product->get_id(), 1 );
// Set an address so that shipping can be calculated.
add_filter( 'woocommerce_customer_get_shipping_country', array( $this, 'force_customer_us_country' ) );
add_filter( 'woocommerce_customer_get_shipping_state', array( $this, 'force_customer_us_state' ) );
add_filter( 'woocommerce_customer_get_shipping_postcode', array( $this, 'force_customer_us_postcode' ) );
WC_Helper_Shipping::force_customer_us_address();
// Set the flat_rate shipping method.
WC()->session->set( 'chosen_shipping_methods', array( 'flat_rate' ) );
@ -1564,6 +1550,65 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertEquals( 20, WC()->cart->total );
}
/**
* Test that shipping tax rounding does not round down when price are inclusive of taxes.
*/
public function test_calculate_totals_shipping_tax_rounded_26654() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '25.0000',
'tax_rate_name' => 'Tax @ 25%',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '1',
'tax_rate_order' => '1',
'tax_rate_class' => 'standard',
);
WC_Tax::_insert_tax_rate( $tax_rate );
$product = WC_Helper_Product::create_simple_product();
$product->set_regular_price( 242 );
$product->set_tax_class( 'product' );
$product->save();
WC_Helper_Shipping::create_simple_flat_rate( 75.10 );
WC_Helper_Shipping::force_customer_us_address();
WC()->cart->empty_cart();
WC()->cart->add_to_cart( $product->get_id(), 1 );
WC()->session->set( 'chosen_shipping_methods', array( 'flat_rate' ) );
WC()->cart->calculate_totals();
$this->assertEquals( 18.775, WC()->cart->get_shipping_tax() );
$this->assertEquals( 335.88, WC()->cart->get_total( 'edit' ) );
$this->assertEquals( 67.18, WC()->cart->get_taxes_total() );
$checkout = WC_Checkout::instance();
$order = new WC_Order();
$checkout->set_data_from_cart( $order );
$this->assertEquals( 67.18, $order->get_total_tax() );
$this->assertEquals( 335.88, $order->get_total() );
$this->assertEquals( 18.775, $order->get_shipping_tax() );
update_option( 'woocommerce_tax_round_at_subtotal', 'no' );
WC()->cart->calculate_totals();
$this->assertEquals( 18.78, WC()->cart->get_shipping_tax() );
$this->assertEquals( 335.88, WC()->cart->get_total( 'edit' ) );
$this->assertEquals( 67.18, WC()->cart->get_taxes_total() );
$order = new WC_Order();
$checkout->set_data_from_cart( $order );
$this->assertEquals( 67.18, $order->get_total_tax() );
$this->assertEquals( 335.88, $order->get_total() );
$this->assertEquals( 18.78, $order->get_shipping_tax() );
}
/**
* Test cart fee.
*