Skip to content

Commit 5dfd3d3

Browse files
committed
Estimate correct tax_rate
1 parent 9642cb0 commit 5dfd3d3

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

src/Api/ApiClient.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function orderLines(?Cart $cart): array
5757
}
5858

5959
foreach ($cart->getItems() as $item) {
60-
$itemDto = new Item($item, $cart);
60+
$itemDto = new Item($item, $cart, $cart->getBillingAddress(), $cart->getShippingAddress());
6161

6262
$orderLines[] = $this->serializer->normalize($itemDto);
6363
}

src/Dto/Item.php

+69-10
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,20 @@
2020
use Contao\StringUtil;
2121
use Isotope\Interfaces\IsotopeProduct;
2222
use Isotope\Interfaces\IsotopeProductCollection;
23+
use Isotope\Model\Address;
2324
use Isotope\Model\Config as IsotopeConfig;
2425
use Isotope\Model\Product;
2526
use Isotope\Model\ProductCollectionItem;
27+
use Isotope\Model\ProductCollectionSurcharge;
2628
use Isotope\Model\ProductCollectionSurcharge\Rule;
29+
use Isotope\Model\ProductPrice;
2730
use Isotope\Model\ProductType;
31+
use Isotope\Model\TaxClass;
32+
use Isotope\Model\TaxRate;
2833

2934
final class Item extends AbstractOrderLine
3035
{
31-
public function __construct(ProductCollectionItem $item, IsotopeProductCollection $collection)
36+
public function __construct(ProductCollectionItem $item, IsotopeProductCollection $collection, Address $billingAddress, Address $shippingAddress)
3237
{
3338
$this->reference = $item->getSku();
3439
$this->name = $item->getName();
@@ -38,18 +43,69 @@ public function __construct(ProductCollectionItem $item, IsotopeProductCollectio
3843
$this->unit_price = (int) round($item->getPrice() * 100);
3944
$this->total_amount = (int) round(($item->getTotalPrice() - $this->total_discount_amount / 100) * 100);
4045

41-
// If config shows net prices, tax_rate stays 0 because sales taxes are added as surcharges.
42-
// If config shows gross prices, calculate tax_amount and tax_rate.
43-
if (IsotopeConfig::PRICE_DISPLAY_GROSS === $collection->getConfig()->priceDisplay) {
44-
$this->total_tax_amount = (int) round(($item->getTotalPrice() - $item->getTaxFreeTotalPrice()) * 100);
45-
$this->tax_rate = (int) round(($this->total_tax_amount / ($this->total_amount - $this->total_tax_amount) * 10000));
46-
}
46+
$this->addTaxRate($item, $collection, $billingAddress, $shippingAddress);
47+
$this->addTotalTaxAmount($item, $collection);
4748

4849
$this->addType($item);
4950
$this->addProductUrl($item);
5051
$this->addImageUrl($item);
5152
}
5253

54+
private function addTaxRate(ProductCollectionItem $item, IsotopeProductCollection $collection, Address $billingAddress, Address $shippingAddress)
55+
{
56+
// If config shows net prices, tax_rate stays 0 because sales taxes are added as surcharges.
57+
// If config shows gross prices, calculate tax_amount and tax_rate.
58+
if (IsotopeConfig::PRICE_DISPLAY_GROSS !== $collection->getConfig()->priceDisplay) {
59+
return;
60+
}
61+
62+
/** @var ProductPrice&Model $price */
63+
$price = $item->getProduct()->getPrice();
64+
/** @var TaxClass&Model $taxClass */
65+
$taxClass = $price->getRelated('tax_class');
66+
if (null === $taxClass) {
67+
return;
68+
}
69+
70+
/** @var TaxRate&Model $includedRate */
71+
$includedRate = $taxClass->getRelated('includes');
72+
$addresses = ['billing' => $billingAddress, 'shipping' => $shippingAddress];
73+
74+
// Use the tax rate that is included in the price, if applicable
75+
if (null !== $includedRate
76+
&& $includedRate->isApplicable($item->getTotalPrice(), $addresses)
77+
&& $includedRate->isPercentage()) {
78+
$this->tax_rate = (int) round($includedRate->getAmount() * 100);
79+
80+
return;
81+
}
82+
83+
// Use the tax rate that is added to price, first applicable wins.
84+
// We purposely do not support cases when multiple tax rates are applicable to a single product,
85+
// or non-percantage tax_rates apply.
86+
if (null !== ($addedRates = $taxClass->getRelated('rates'))) {
87+
/** @var TaxRate $taxRate */
88+
foreach ($addedRates as $taxRate) {
89+
if ($taxRate->isApplicable($item->getTotalPrice(), $addresses) && $taxRate->isPercentage()) {
90+
$this->tax_rate = (int) round($taxRate->getAmount() * 100);
91+
92+
return;
93+
}
94+
}
95+
}
96+
}
97+
98+
private function addTotalTaxAmount(ProductCollectionItem $item, IsotopeProductCollection $collection)
99+
{
100+
// If config shows net prices, tax_rate stays 0 because sales taxes are added as surcharges.
101+
// If config shows gross prices, calculate tax_amount and tax_rate.
102+
if (IsotopeConfig::PRICE_DISPLAY_GROSS !== $collection->getConfig()->priceDisplay) {
103+
return;
104+
}
105+
106+
$this->total_tax_amount = (int) ($this->total_amount - $this->total_amount * 10000 / (10000 + $this->tax_rate));
107+
}
108+
53109
private function addType(ProductCollectionItem $item)
54110
{
55111
$this->type = self::TYPE_PHYSICAL;
@@ -65,11 +121,13 @@ private function addType(ProductCollectionItem $item)
65121

66122
private function addProductUrl(ProductCollectionItem $item)
67123
{
124+
/** @var ProductCollectionItem&Model $item */
68125
$product = $item->getProduct();
69126
$jumpTo = $item->getRelated('jumpTo');
70-
if (null !== $jumpTo && null !== $product
71-
&& $item->hasProduct()
72-
&& $product->isAvailableInFrontend()) {
127+
if (null !== $jumpTo
128+
&& null !== $product
129+
&& $item->hasProduct()
130+
&& $product->isAvailableInFrontend()) {
73131
$this->product_url = Environment::get('url').'/'.$product->generateUrl($jumpTo);
74132
}
75133
}
@@ -100,6 +158,7 @@ private function addImageUrl(ProductCollectionItem $item)
100158
private function addTotalDiscountAmount(array $surcharges, ProductCollectionItem $item): void
101159
{
102160
foreach ($surcharges as $surcharge) {
161+
/** @var ProductCollectionSurcharge&Model $surcharge */
103162
if (!$surcharge instanceof Rule || ('cart' === $surcharge->type && 'subtotal' === $surcharge->applyTo)) {
104163
continue;
105164
}

0 commit comments

Comments
 (0)