Skip to content

In Magento 2, I used the Magento 2 TaxCloud extension, but after placing an order, the tax shows as a random amount or sometimes as 0

<?php
/**
 * Taxcloud_Magento2
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * @package    Taxcloud_Magento2
 * @author     TaxCloud <[email protected]>
 * @copyright  2021 The Federal Tax Authority, LLC d/b/a TaxCloud
 * @license    http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 */

namespace TaxcloudMagento2Model;

use MagentoCustomerApiDataAddressInterfaceFactory;
use MagentoCustomerApiDataRegionInterfaceFactory;
use MagentoFrameworkAppConfigScopeConfigInterface;
use MagentoFrameworkSerializeSerializerJson;
use MagentoQuoteApiDataShippingAssignmentInterface;
use MagentoQuoteModelQuote;
use MagentoQuoteModelQuoteAddressTotal;
use MagentoStoreModelScopeInterface;
use MagentoTaxApiDataQuoteDetailsInterfaceFactory;
use MagentoTaxApiDataQuoteDetailsItemInterfaceFactory;
use MagentoTaxApiDataTaxClassKeyInterfaceFactory;
use MagentoTaxApiTaxCalculationInterface;
use MagentoTaxHelperData;
use MagentoTaxModelConfig;
use TaxcloudMagento2LoggerLogger;

/**
 * Tax totals calculation model
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Tax extends MagentoTaxModelSalesTotalQuoteTax
{

    /**
     * Magento Config Object
     *
     * @var ScopeConfigInterface|null
     */
    protected ?ScopeConfigInterface $_scopeConfig = null;

    /**
     * TaxCloud Api Object
     *
     * @var Api
     */
    protected $_tcapi;

    /**
     * TaxCloud Logger
     *
     * @var Logger
     */
    protected $_tclogger;

    /**
     * Class constructor
     *
     * @param Config $taxConfig
     * @param TaxCalculationInterface $taxCalculationService
     * @param QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory
     * @param QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory
     * @param TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory
     * @param AddressInterfaceFactory $customerAddressFactory
     * @param RegionInterfaceFactory $customerAddressRegionFactory
     * @param Data $taxData
     * @param Json|null $serializer
     * @param ScopeConfigInterface $scopeConfig
     * @param Api $tcapi
     * @param Logger $tclogger
     */
    public function __construct(
        Config                           $taxConfig,
        TaxCalculationInterface          $taxCalculationService,
        QuoteDetailsInterfaceFactory     $quoteDetailsDataObjectFactory,
        QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory,
        TaxClassKeyInterfaceFactory      $taxClassKeyDataObjectFactory,
        AddressInterfaceFactory          $customerAddressFactory,
        RegionInterfaceFactory           $customerAddressRegionFactory,
        Data                             $taxData,
        Json                             $serializer = null,
        ScopeConfigInterface             $scopeConfig,
        Api                              $tcapi,
        Logger                           $tclogger
    ) {
        $this->_scopeConfig = $scopeConfig;
        $this->_tcapi = $tcapi;

        if ($scopeConfig->getValue('tax/taxcloud_settings/logging', ScopeInterface::SCOPE_STORE)) {
            $this->_tclogger = $tclogger;
        } else {
            $this->_tclogger = new class() {
                public function info()
                {
                }
            };
        }

        parent::__construct(
            $taxConfig,
            $taxCalculationService,
            $quoteDetailsDataObjectFactory,
            $quoteDetailsItemDataObjectFactory,
            $taxClassKeyDataObjectFactory,
            $customerAddressFactory,
            $customerAddressRegionFactory,
            $taxData,
            $serializer
        );
    }

    /**
     * Collect tax totals for quote address
     *
     * @param Quote $quote
     * @param ShippingAssignmentInterface $shippingAssignment
     * @param Total $total
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function collect(
        Quote $quote,
        ShippingAssignmentInterface $shippingAssignment,
        Total $total
    ): static {
        if (!$this->_scopeConfig->getValue('tax/taxcloud_settings/enabled', ScopeInterface::SCOPE_STORE)) {
            return parent::collect($quote, $shippingAssignment, $total);
        }

        $this->clearValues($total);
        if (!$shippingAssignment->getItems()) {
            return $this;
        }

        $baseTaxDetails = $this->getQuoteTaxDetails($shippingAssignment, $total, true);
        $taxDetails = $this->getQuoteTaxDetails($shippingAssignment, $total, false);

        //Populate address and items with tax calculation results
        $itemsByType = $this->organizeItemTaxDetailsByType($taxDetails, $baseTaxDetails);

        // Fetch tax amount from TaxCloud
        $taxAmounts = $this->_tcapi->lookupTaxes($itemsByType, $shippingAssignment, $quote);
        // $this->_tclogger->info(json_encode($taxAmounts, JSON_PRETTY_PRINT));

        $keyedAddressItems = [];
        foreach ($shippingAssignment->getItems() as $item) {
            $keyedAddressItems[$item->getTaxCalculationItemId()] = $item;
        }

        if (isset($itemsByType[self::ITEM_TYPE_PRODUCT])) {
            foreach ($itemsByType[self::ITEM_TYPE_PRODUCT] as $code => $itemTaxDetail) {
                
                $taxDetail = $itemTaxDetail[self::KEY_ITEM];
                $baseTaxDetail = $itemTaxDetail[self::KEY_BASE_ITEM];

                $quoteItem = $keyedAddressItems[$code];

                if ($quoteItem->getProduct()->getTaxClassId() === '0' || $quoteItem->getQty() === 0) {
                    $taxAmount = 0;
                    $taxAmountPer = 0;
                } else {
                    $taxAmount = $taxAmounts[self::ITEM_TYPE_PRODUCT][$code] ?? 0;
                    $taxAmountPer = $taxAmount / $quoteItem->getQty();
                }

                $taxDetail->setRowTax($taxAmount);
                $taxDetail->setPriceInclTax($taxDetail->getPrice() + $taxAmountPer);
                $taxDetail->setRowTotalInclTax($taxDetail->getRowTotal() + $taxAmount);
                $taxDetail->setAppliedTaxes([]);
                if ($taxDetail->getRowTotal() > 0) {
                    $taxDetail->setTaxPercent(round(100 * $taxDetail->getRowTax() / $taxDetail->getRowTotal(), 2));
                } else {
                    $taxDetail->setTaxPercent(0);
                }

                $baseTaxDetail->setRowTax($taxAmount);
                $baseTaxDetail->setPriceInclTax($baseTaxDetail->getPrice() + $taxAmountPer);
                $baseTaxDetail->setRowTotalInclTax($baseTaxDetail->getRowTotal() + $taxAmount);
                $baseTaxDetail->setAppliedTaxes([]);
                if ($baseTaxDetail->getRowTotal() > 0) {
                    $baseTaxDetail->setTaxPercent(
                        round(100 * $baseTaxDetail->getRowTax() / $baseTaxDetail->getRowTotal(), 2)
                    );
                } else {
                    $baseTaxDetail->setTaxPercent(0);
                }
            }

            $this->processProductItems($shippingAssignment, $itemsByType[self::ITEM_TYPE_PRODUCT], $total);
        }

        if (isset($itemsByType[self::ITEM_TYPE_SHIPPING])) {
            $shippingTaxDetails = $itemsByType[self::ITEM_TYPE_SHIPPING][self::ITEM_CODE_SHIPPING][self::KEY_ITEM];
            $baseShippingTaxDetails = $itemsByType[self::ITEM_TYPE_SHIPPING][self::ITEM_CODE_SHIPPING][self::KEY_BASE_ITEM];

            $taxAmount = $taxAmounts[self::ITEM_TYPE_SHIPPING];
            $taxAmountPer = $taxAmount / 1;

            $shippingTaxDetails->setRowTax($taxAmount);
            $shippingTaxDetails->setPriceInclTax($shippingTaxDetails->getPrice() + $taxAmountPer);
            $shippingTaxDetails->setRowTotalInclTax($shippingTaxDetails->getRowTotal() + $taxAmount);
            $shippingTaxDetails->setAppliedTaxes([]);
            if ($shippingTaxDetails->getRowTotal() > 0) {
                $shippingTaxDetails->setTaxPercent(
                    round(
                        100 * $shippingTaxDetails->getRowTax() / $shippingTaxDetails->getRowTotal(),
                        2
                    )
                );
            } else {
                $shippingTaxDetails->setTaxPercent(0);
            }

            $baseShippingTaxDetails->setRowTax($taxAmount);
            $baseShippingTaxDetails->setPriceInclTax($baseShippingTaxDetails->getPrice() + $taxAmountPer);
            $baseShippingTaxDetails->setRowTotalInclTax($baseShippingTaxDetails->getRowTotal() + $taxAmount);
            $baseShippingTaxDetails->setAppliedTaxes([]);
            if ($baseShippingTaxDetails->getRowTotal() > 0) {
                $baseShippingTaxDetails->setTaxPercent(
                    round(100 * $baseShippingTaxDetails->getRowTax() / $baseShippingTaxDetails->getRowTotal(), 2)
                );
            } else {
                $baseShippingTaxDetails->setTaxPercent(0);
            }

            $this->processShippingTaxInfo($shippingAssignment, $total, $shippingTaxDetails, $baseShippingTaxDetails);
        }

        //Process taxable items that are not product or shipping
        $this->processExtraTaxables($total, $itemsByType);

        //Save applied taxes for each item and the quote in aggregation
        $this->processAppliedTaxes($total, $shippingAssignment, $itemsByType);

        if ($this->includeExtraTax()) {
            $total->addTotalAmount('extra_tax', $total->getExtraTaxAmount());
            $total->addBaseTotalAmount('extra_tax', $total->getBaseExtraTaxAmount());
        }

        return $this;
    }
}