-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Description
Tax is not correctly calculated when "Apply Tax On" option is set to "Original price only." Tax is always based on item calculation price (custom price).
Preconditions
- Magento Open Source 2.2.3
- PHP 7.1.14
- Apache 2.2.15
- Magento sample data installed
- Shipping methods Flat Rate, Table Rates, and UPS enabled
- Payment methods Check/Money Order, Credit Card (Braintree) enabled
- Setting "Base Currency" set to
US Dollar - Setting "Default Display Currency" set to
US Dollar - Setting "Tax Calculation Method Based On" set to
Total - Setting "Tax Calculation Based On" set to
Shipping Address - Setting "Shipping Tax Class" set to
Taxable Goods - Setting "Apply Tax On" set to
Original price only - New tax rate configured as: any zip code match (*), region of PA/US, rate of 6.0000
- Existing tax rule "Rule1" expanded to accept new tax rate
Steps to reproduce
- From the admin panel, go to Sales > Orders
- Select Create New Order
- Choose existing customer Veronica Costello
- Click Add Products
- Select simple product Overnight Duffle ($45.00)
- Click Add Selected Product(s) to Order
- In the Shipping Address section, uncheck option Same As Billing Address
- Change the Street Address to 123 Main Street
- Change the City to Mechanicsburg
- Change the State to Pennsylvania
- Change the Zip Code to 17055
- Observe Tax line-item reads $2.70 (0.06 * 45 = 2.6999)
- For quote item Overnight Duffle, select option Custom Price
- Enter value 10.00
- Click Update Items and Quantities
- Observe Tax line-item reads $2.70 (0.06 * 45 = 2.6999)
Expected result
- Final tax line-item reads $2.70
Actual result
- Final tax line-item reads $0.60
Additional Information
Class Magento\Tax\Model\Sales\Total\Quote\CommonTaxCollector is the underlying processor for the total and subtotal tax collectors. This class declares method mapItem, and this method is responsible for converting quote items into a more specific instance of Magento\Tax\Api\Data\QuoteDetailsItemInterface. Lines 216-228 determine the unit price of the given item:
if ($useBaseCurrency) {
if (!$item->getBaseTaxCalculationPrice()) {
$item->setBaseTaxCalculationPrice($item->getBaseCalculationPriceOriginal());
}
$itemDataObject->setUnitPrice($item->getBaseTaxCalculationPrice())
->setDiscountAmount($item->getBaseDiscountAmount());
} else {
if (!$item->getTaxCalculationPrice()) {
$item->setTaxCalculationPrice($item->getCalculationPriceOriginal());
}
$itemDataObject->setUnitPrice($item->getTaxCalculationPrice())
->setDiscountAmount($item->getDiscountAmount());
}
My understanding is as follows:
- Look for presence of
tax_calculation_pricevia magic getter (note: no implementation found) - If not set, use
calculation_pricevia methodAbstractItem::getCalculationPriceOriginal
Given the name "tax calculation price," it can be assumed that any modifications to the calculation price should be made here for the sake of tax-specific constraints. Yet, there are none made. Instead the original calculation price is used as-is.
This becomes problematic under the following conditions:
- Method
AbstractItem::setCustomPriceis used; and, - Tax configuration is set to "Apply Tax On" the original price
These conditions cause the mapItem method to incorrectly refer to a price that is different from the originating product's base price. That is, a custom price. Furthermore, the "Apply Tax On" setting itself appears to be unused in the core altogether. There are even helper methods in Magento\Tax\Helper\Data, applyTaxOnCustomPrice and applyTaxOnOriginalPrice, which appear to be useful to this mapping process.
I believe the solution is to conditionally fall back to AbstractItem::getCalculationPriceOriginal only when the setting "Apply Tax On" is set to "Custom price if available." Otherwise, if set to "Original price only," then the tax calculation price must be derived from AbstractItem::getBaseOriginalPrice.