/**
  * Create AppliedTax data object based on applied tax rates and tax amount
  *
  * @param float $rowTax
  * @param float $totalTaxRate
  * @param array $appliedRates May contain multiple tax rates when catalog price includes tax
  * example:
  *  [
  *      [
  *          'id' => 'id1',
  *          'percent' => 7.5,
  *          'rates' => [
  *              'code' => 'code1',
  *              'title' => 'title1',
  *              'percent' => 5.3,
  *          ],
  *      ],
  *      [
  *          'id' => 'id2',
  *          'percent' => 8.5,
  *          'rates' => [
  *              'code' => 'code2',
  *              'title' => 'title2',
  *              'percent' => 7.3,
  *          ],
  *      ],
  *  ]
  * @return \Magento\Tax\Api\Data\AppliedTaxInterface[]
  */
 protected function getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates)
 {
     /** @var \Magento\Tax\Api\Data\AppliedTaxInterface[] $appliedTaxes */
     $appliedTaxes = [];
     $totalAppliedAmount = 0;
     foreach ($appliedRates as $appliedRate) {
         if ($appliedRate['percent'] == 0) {
             continue;
         }
         $appliedAmount = $rowTax / $totalTaxRate * $appliedRate['percent'];
         //Use delta rounding to split tax amounts for each tax rates between items
         $appliedAmount = $this->deltaRound($appliedAmount, $appliedRate['id'], true, self::KEY_APPLIED_TAX_DELTA_ROUNDING);
         if ($totalAppliedAmount + $appliedAmount > $rowTax) {
             $appliedAmount = $rowTax - $totalAppliedAmount;
         }
         $totalAppliedAmount += $appliedAmount;
         $appliedTaxDataObject = $this->appliedTaxDataObjectFactory->create();
         $appliedTaxDataObject->setAmount($appliedAmount);
         $appliedTaxDataObject->setPercent($appliedRate['percent']);
         $appliedTaxDataObject->setTaxRateKey($appliedRate['id']);
         /** @var  \Magento\Tax\Api\Data\AppliedTaxRateInterface[] $rateDataObjects */
         $rateDataObjects = [];
         foreach ($appliedRate['rates'] as $rate) {
             //Skipped position, priority and rule_id
             $rateDataObjects[$rate['code']] = $this->appliedTaxRateDataObjectFactory->create()->setPercent($rate['percent'])->setCode($rate['code'])->setTitle($rate['title']);
         }
         $appliedTaxDataObject->setRates($rateDataObjects);
         $appliedTaxes[$appliedTaxDataObject->getTaxRateKey()] = $appliedTaxDataObject;
     }
     return $appliedTaxes;
 }
 /**
  * Convert the AvaTax Tax Summary to a Magento object
  *
  * @see \Magento\Tax\Model\Calculation\AbstractCalculator::getAppliedTax()
  *
  * @param GetTaxResult $getTaxResult
  * @param float $rowTax
  * @return \Magento\Tax\Api\Data\AppliedTaxInterface
  */
 protected function getAppliedTax(GetTaxResult $getTaxResult, $rowTax)
 {
     $totalPercent = 0.0;
     $taxNames = [];
     /** @var  \Magento\Tax\Api\Data\AppliedTaxRateInterface[] $rateDataObjects */
     $rateDataObjects = [];
     /**
      * There are rare situations in which the Tax Summary from AvaTax will contain items that have the same TaxName,
      * JurisCode, JurisName, and Rate. e.g., an order with shipping with VAT tax from Germany (example below).
      * To account for this, we need to group rates by a combination of JurisCode and JurisName. Otherwise the same
      * rate will get added twice. This is problematic for two reasons:
      *     1. If a merchant has configured Magento to "Display Full Tax Summary" then the user will see the same
      *        same rate with the same percentage displayed twice. This will be confusing.
      *     2. When an order is placed, the \Magento\Tax\Model\Plugin\OrderSave::saveOrderTax method populates the
      *        sales_order_tax[_item] tables with information based on the information contained in the Applied Taxes
      *        array. Having duplicates rates will throw things off.
      *
      * 'TaxSummary' => ['TaxDetail' => [
      *     // This rate was applied to shipping
      *     0 => [
      *         'JurisType' => 'State',
      *         'JurisCode' => 'DE',
      *         'TaxType' => 'Sales',
      *         'Taxable' => '20',
      *         'Rate' => '0.189995',
      *         'Tax' => '3.8',
      *         'JurisName' => 'GERMANY',
      *         'TaxName' => 'Standard Rate',
      *         'Country' => 'DE',
      *         'Region' => 'DE',
      *         'TaxCalculated' => '3.8',
      *     ],
      *     // This rate was applied to products
      *     1 => [
      *         'JurisType' => 'State',
      *         'JurisCode' => 'DE',
      *         'TaxType' => 'Sales',
      *         'Base' => '150',
      *         'Taxable' => '150',
      *         'Rate' => '0.190000',
      *         'Tax' => '28.5',
      *         'JurisName' => 'GERMANY',
      *         'TaxName' => 'Standard Rate',
      *         'Country' => 'DE',
      *         'Region' => 'DE',
      *         'TaxCalculated' => '28.5',
      *     ]
      * ]]
      */
     $taxRatesByCode = [];
     /* @var \AvaTax\TaxDetail $row */
     foreach ($getTaxResult->getTaxSummary() as $key => $row) {
         $arrayKey = $row->getJurisCode() . '_' . $row->getJurisName();
         // Since the total percent is for display purposes only, round to 5 digits. Since the tax percent returned
         // from AvaTax is not the actual tax rate, but the effective rate, rounding makes the presentation make more
         // sense to the user. For example, a tax rate may be 19%, but AvaTax may return a value of 0.189995.
         $roundedRate = round((double) $row->getRate(), 4);
         $ratePercent = $roundedRate * Tax::RATE_MULTIPLIER;
         if (!isset($taxRatesByCode[$arrayKey])) {
             $taxRatesByCode[$arrayKey] = ['id' => $key . '_' . $row->getJurisCode(), 'ratePercent' => $ratePercent, 'taxName' => $row->getTaxName(), 'jurisCode' => $row->getJurisCode(), 'taxable' => (double) $row->getTaxable(), 'tax' => (double) $row->getTax()];
         } elseif ($taxRatesByCode[$arrayKey]['ratePercent'] != $ratePercent) {
             /**
              * There are rare situations in which a duplicate rate will have a slightly different percentage (see
              * example in DocBlock above). In these cases, we will just determine the "effective" rate" ourselves.
              */
             $taxRatesByCode[$arrayKey]['taxable'] += (double) $row->getTaxable();
             $taxRatesByCode[$arrayKey]['tax'] += (double) $row->getTax();
             // Avoid division by 0
             if ($taxRatesByCode[$arrayKey]['taxable'] > 0) {
                 $blendedRate = $taxRatesByCode[$arrayKey]['tax'] / $taxRatesByCode[$arrayKey]['taxable'];
                 $taxRatesByCode[$arrayKey]['ratePercent'] = $blendedRate;
             }
         }
     }
     foreach ($taxRatesByCode as $rowArray) {
         $ratePercent = $rowArray['ratePercent'];
         $totalPercent += $ratePercent;
         $taxCode = $rowArray['jurisCode'];
         $taxName = $rowArray['taxName'];
         $taxNames[] = $rowArray['taxName'];
         // In case jurisdiction codes are duplicated, prepending the $key ensures we have a unique ID
         $id = $rowArray['id'];
         // Skipped position, priority and rule_id
         $rateDataObjects[$id] = $this->appliedTaxRateDataObjectFactory->create()->setPercent($ratePercent)->setCode($taxCode)->setTitle($taxName);
     }
     $rateKey = implode(' - ', $taxNames);
     $appliedTaxDataObject = $this->appliedTaxDataObjectFactory->create();
     $appliedTaxDataObject->setAmount($rowTax);
     $appliedTaxDataObject->setPercent($totalPercent);
     $appliedTaxDataObject->setTaxRateKey($rateKey);
     $appliedTaxDataObject->setRates($rateDataObjects);
     return $appliedTaxDataObject;
 }