From e00c3450c5d7edd172216a741587236d77e1e596 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 23 May 2016 16:56:31 +0100 Subject: [PATCH] Discount rounding logic improvements Instead of rounding the single item discount amount, this rounds the line to the store DP setting before running tax logic. Fixes #10963 Also works with #10573 Includes unit tests --- includes/class-wc-cart.php | 24 ++++++++++++++------ includes/class-wc-coupon.php | 2 +- tests/unit-tests/cart/cart.php | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 86a1d6ecead..4beb6089b8f 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -1255,7 +1255,7 @@ class WC_Cart { $line_subtotal_tax = 0; $line_subtotal = $line_price; $line_tax = 0; - $line_total = WC_Tax::round( $discounted_price * $values['quantity'] ); + $line_total = round( $discounted_price * $values['quantity'], WC_ROUNDING_PRECISION ); /** * Prices include tax. @@ -1285,11 +1285,16 @@ class WC_Cart { // Adjusted price (this is the price including the new tax rate) $adjusted_price = ( $line_subtotal + $line_subtotal_tax ) / $values['quantity']; - // Apply discounts + // Apply discounts and get the discounted price FOR A SINGLE ITEM $discounted_price = $this->get_discounted_price( $values, $adjusted_price, true ); - $discounted_taxes = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates, true ); + + // Convert back to line price and round nicely + $discounted_line_price = round( $discounted_price * $values['quantity'], $this->dp ); + + // Now use rounded line price to get taxes. + $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true ); $line_tax = array_sum( $discounted_taxes ); - $line_total = ( $discounted_price * $values['quantity'] ) - $line_tax; + $line_total = $discounted_line_price - $line_tax; /** * Regular tax calculation (customer inside base and the tax class is unmodified. @@ -1305,9 +1310,14 @@ class WC_Cart { // Calc prices and tax (discounted) $discounted_price = $this->get_discounted_price( $values, $base_price, true ); - $discounted_taxes = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates, true ); - $line_tax = array_sum( $discounted_taxes ); - $line_total = ( $discounted_price * $values['quantity'] ) - $line_tax; + + // Convert back to line price and round nicely + $discounted_line_price = round( $discounted_price * $values['quantity'], $this->dp ); + + // Now use rounded line price to get taxes. + $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true ); + $line_tax = array_sum( $discounted_taxes ); + $line_total = $discounted_line_price - $line_tax; } // Tax rows - merge the totals we just got diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index 35b3b03372a..4428c34ee58 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -748,7 +748,7 @@ class WC_Coupon { } } - $discount = wc_cart_round_discount( $discount, wc_get_price_decimals() ); + $discount = wc_cart_round_discount( $discount, WC_ROUNDING_PRECISION ); return apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $discounting_amount, $cart_item, $single, $this ); } diff --git a/tests/unit-tests/cart/cart.php b/tests/unit-tests/cart/cart.php index fc3cbab008e..f43df2a1d80 100644 --- a/tests/unit-tests/cart/cart.php +++ b/tests/unit-tests/cart/cart.php @@ -15,11 +15,15 @@ class WC_Tests_Cart extends WC_Unit_Test_Case { * Due to discounts being split amongst products in cart. */ public function test_cart_get_discounted_price() { + global $wpdb; + // We need this to have the calculate_totals() method calculate totals if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { define( 'WOOCOMMERCE_CHECKOUT', true ); } + # Test case 1 #10963 + // Create dummy coupon - fixed cart, 1 value $coupon = WC_Helper_Coupon::create_coupon(); @@ -51,6 +55,42 @@ class WC_Tests_Cart extends WC_Unit_Test_Case { WC()->cart->empty_cart(); WC()->cart->remove_coupons(); + # Test case 2 #10573 + update_post_meta( $product->id, '_regular_price', '29.95' ); + update_post_meta( $product->id, '_price', '29.95' ); + update_post_meta( $coupon->id, 'discount_type', 'percent' ); + update_post_meta( $coupon->id, 'coupon_amount', '10' ); + update_option( 'woocommerce_prices_include_tax', 'yes' ); + update_option( 'woocommerce_calc_taxes', 'yes' ); + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '10.0000', + 'tax_rate_name' => 'TAX', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '' + ); + WC_Tax::_insert_tax_rate( $tax_rate ); + $product = wc_get_product( $product->id ); + + WC()->cart->add_to_cart( $product->id, 1 ); + WC()->cart->add_discount( $coupon->code ); + + WC()->cart->calculate_totals(); + $cart_item = current( WC()->cart->get_cart() ); + $this->assertEquals( '24.51', number_format( $cart_item['line_total'], 2, '.', '' ) ); + + // Cleanup + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" ); + WC()->cart->empty_cart(); + WC()->cart->remove_coupons(); + update_option( 'woocommerce_prices_include_tax', 'no' ); + update_option( 'woocommerce_calc_taxes', 'no' ); + // Delete coupon WC_Helper_Coupon::delete_coupon( $coupon->id );