Problem/Motivation
Given a store with french VAT, price included tax, in the checkout, if the client select (or Fill) an Address in another Country than FR or EU (test with USA), in the order infromation step, when going to the next step (Review) then we've got a fatal error.
TypeError : Argument 1 passed to Drupal\commerce_product_tax\Resolver\TaxRateResolver::setTaxType() must implement interface Drupal\commerce_tax\Entity\TaxTypeInterface, null given, called in /private/var/www/usine_mercuris/web/modules/contrib/commerce/modules/tax/src/Resolver/ChainTaxRateResolver.php on line 51 dans Drupal\commerce_product_tax\Resolver\TaxRateResolver->setTaxType() (
The store is configured for the Tax settings, with France as Tax registrations.
I'am using the Commerce Product Tax module, which provide a Resolver which is Tax Type Awre, ans so trigger the fatal error.
Looks like the root cause is on TaxOrderProcessor->getDefaultRates() in commerce_tax module
foreach ($this->getStoreZones($store) as $zone) {
$rate = $this->chainRateResolver->resolve($zone, $order_item, $store_profile);
if (is_object($rate)) {
$rates[$zone->getId()] = $rate;
}
}
The chainRateResolver is called but without setting the taxType as does the LocalTaxTypeBase->resolveRates()
// Provide the tax type entity to the resolvers.
$this->chainRateResolver->setTaxType($this->parentEntity);
If the customer country is not VAT applicable, as the USA for French shop, so the $tax_adjustments is empty.
if (empty($tax_adjustments)) {
$unit_price = $order_item->getUnitPrice();
$rates = $this->getDefaultRates($order_item, $store);
...
}
And then we call the getDefaultRates() method. And then we call the chainTaxRateResolver service but without any tax_type provided, and if some resolvers implements TaxTypeAwareInterface then everything breaks
Proposed resolution
The tricky part is now : how to provide the right tax_type to the getDefaultsRates() method.
If we have two tax type enabled on the site, let's say a "european_union_vat" tax type and a "shipping" tax type, the eu_vat is resolved correctly (let's say the reduced rate), but the shipping tax type is resolved to the default rate (standard).
And we can't know which is the right one to use.
I don't see any solutions by checking if taxType is NULL or not in the chainTaxRateResolver. Because the resolvers that are tax type aware need anyway this tax type and are called too anyway by the chainTaxRateResolver.
The only viable solution I can see should be to add a new settings on Tax Type, let's say "pass this tax type to resolvers in case tax not applicable", or even a settings 'Default Tax Type" and then we could iterable again on the tax types, if $tax_adjustments is empty and price include tax, to be able to collect the right tax types (configurable) and call the getDefaultsRate() with those tax type, and be able to remove adjustments from the unit price.
From comment #22
We need to split getStoreZones() into getStoreTaxType($store) and getStoreZones($store, $tax_type), and call both from getDefaultRates().
We'll then have the $tax_type to set on the resolvers.
Remaining tasks
- demonstrate the fatal error with tax type aware test
- find the right way to solve this issue :-)
- write the patch
User interface changes
(None
API changes
A new setting on the Tax Type ?
Data model changes
A new setting on the Tax Type ?
Comment | File | Size | Author |
---|---|---|---|
#39 | 3113768-39-fix-tax-crash.patch | 5.95 KB | bojanz |
| |||
#33 | 3113768-interdiff-28-33.txt | 1.25 KB | flocondetoile |
#33 | 3113768-33.patch | 11.96 KB | flocondetoile |
| |||
#28 | 3113768-interdiff-25-28.txt | 702 bytes | flocondetoile |
#28 | 3113768-28.patch | 11.96 KB | flocondetoile |
|
Comments
Comment #2
flocondetoileComment #3
flocondetoileNot sure this issue is related to this module.
Looks like the root cause is on TaxOrderProcessor->getDefaultRates() in commerce_tax module
The chainRateResolver is called but without setting the taxType as does the LocalTaxTypeBase->resolveRates()
Comment #4
flocondetoileComment #5
flocondetoileMoving the issue in the right queue
Comment #6
flocondetoileIf the country is not VAT applicable, as the USA for French shop, so the $tax_adjustments is empty.
And then we call the getDefaultRates() method. And then we call the chainTaxRateResolver service but without any tax_type provided, and if some resolvers implements TaxTypeAwareInterface then everything breaks
Comment #7
flocondetoileComment #8
flocondetoileComment #9
flocondetoileThe tricky part is now : how to provide the right tax_type to the getDefaultsRates() method.
If we have two tax type enabled on the site, let's say a "european_union_vat" tax type and a "shipping" tax type, the eu_vat is resolved correctly (let's say the reduced rate), but the shipping tax type is resolved to the default rate (standard).
And we can't know which is the right one to use.
Comment #10
flocondetoileA first and quick patch. Still needs work and tests. So I let the issue in Active status
Comment #11
mglamanI know how you can reproduce.
LocalTaxBase sets the tax type that gets passed down.
Tax type is null. System goes boom
I recently had this happen too and couldn't figure out how reproduce to save my life. But there's a singular way the tax type is set and then used later on. We need to do checking if the tax type is null
Comment #12
mglamanNo resolver that is tax type aware gets set the tax type by a tax plugin. That's what causes the crash.
US address so EU Vat didn't apply. But the system assumes the tax type would apply. It's like a gap if the tax type didn't avoid applying due to conditions?
Like I said, it definitely happened to me and I couldn't figure out why. But it was a EU site with VAT and I may have had a US address (didn't even think about that)
Comment #13
flocondetoileI don't see any solutions by checking if taxType is NULL or not in the chainTaxRateResolver. Because the resolvers that are tax type aware need anyway this tax type and are called too anyway by the chainTaxRateResolver.
Comment #14
flocondetoileThe only viable solution I can see should be to add a new settings on Tax Type, let's say "pass this tax type to resolvers in case tax not applicable", and then we could iterable again on the tax types, if $tax_adjustments is empty and price include tax, to be able to collect the right tax types (configurable) and call the getDefaultsRate() with those tax type, and be able to remove adjustments from the unit price.
Comment #15
flocondetoileJust to trigger the bot to see what happens
Comment #16
flocondetoileWe need tests about Tax Type Aware.
Comment #17
flocondetoileWorking on this.
Comment #18
flocondetoileComment #19
flocondetoileComment #20
flocondetoileComment #21
flocondetoilesummarize all the debug steps done in the comments into the issue summary.
Comment #22
bojanz CreditAttribution: bojanz at Centarro commentedGreat work chasing this down, flocondetoile. I am very surprised we don't have test coverage for the code in question.
We need to split getStoreZones() into getStoreTaxType($store) and getStoreZones($store, $tax_type), and call both from getDefaultRates().
We'll then have the $tax_type to set on the resolvers.
Comment #23
flocondetoileA test only patch, wich provide a resolver tax type aware and order test integration with this kind of resolvers. Trigger the bot.
Comment #25
flocondetoileThanks for the input @bojanz. Attached the patch with the suggested approach.
Comment #26
flocondetoileForgot the interdiff 23-25
Comment #27
flocondetoileTests are green but
1. may be, the tests would need to be improved by providing a second type of tax like the one provided by the commerce_shipping module ?
2. So the first tax type that matches the shop address is the only one that is ultimately retained. It seems that the order defined in the UI of the tax types is very important. We don't need this following anymore
because we iterate on all the tax types, and the first which match the store adress is set in the $this->$storeZones[$store_id] array which is then defined and returned directly.
Comment #28
flocondetoileComment #29
flocondetoileComment #30
flocondetoileComment #31
flocondetoileTested patch #28 on a real project (the one where I encountered the bug), and looks that all is working fine now.
Also, about
Looks like this is not necessary, as this kind of Tax Type does not implement LocalTaxTypeInterface and so is removed from the tax types collected.
Comment #32
mglamanThank you @flocondetoile! I'll try to look it over more.
Shouldn't we check the proper instance type
s/Awre/Aware
Comment #33
flocondetoilePatch updated with review from #32
Comment #34
sekoz CreditAttribution: sekoz as a volunteer commentedI'm applying that 3113768-33.patch, no other patch on the commerce module.
I tried with EU VAT setup by default and shipping to Switzerland.
Once I reach the /review step I click "Go Back" to /order_information and it deducts the VAT twice from the cart total.
then click "continue to review", then on /review click "go back" again, it keeps subtracting VAT, you can reiterate until it eventually reaches zero or negative.
Drupal core 8.8.2
Drupal Commerce 8.x-2.16
Commerce Product Tax 8.x-1.0-rc1
Comment #35
bojanz CreditAttribution: bojanz at Centarro commented@sekoz
That doesn't sound related to this issue, which is about fixing the crash. Are you using overridden prices by any chance? It sounds like you are.
Comment #36
flocondetoile@sekoz I tried to reproduce your issue without success.
Can you tell us how are configured your store (Prices are entered with taxes included ?, Billing ? Shipping ?) and your vat plugin (Display taxes of this type inclusive in product prices. ?)
Comment #37
sekoz CreditAttribution: sekoz as a volunteer commentedright, prices are entered with taxes included and the "Display taxes of this type inclusive in product prices." box is checked in the Tax type option for EU VAT.
there are shipping fees but no taxes on that shipping.
something else?
Comment #38
bojanz CreditAttribution: bojanz at Centarro commentedHere's an initial review. I'll roll the next patch.
Let's rename this to getDefaultTaxType(StoreInterface $store), so that it matches getDefaultRates().
We should also retain the current one-tax-type-per-store behavior.
Now that we have a static cache for the store tax types, let's remove the static cache for zones.
Speeding up zone matching will be handled generically in #2876974: Statically cache tax zone resolving.
We already have a commerce_tax_test module, let's use it. We try to avoid having multiple test modules when not needed.
This docblock is not correct. I am also wondering if we can simplify the entire resolver to just assert that the tax type is set, since that's all we technically need here.
I would expect this coverage to be a part of the existing OrderIntegrationTest, since that's the kernel test which covers the tax order processor in general.
Comment #39
bojanz CreditAttribution: bojanz at Centarro commentedHere is a reduced version of #33, addressing my review in #38.
I would do #2981436: Create a service for getting the store's default tax type / rates as a followup, adding the additional test coverage that was started here.
Comment #41
bojanz CreditAttribution: bojanz at Centarro commentedCommitted. Thanks, flocondetoile!