You are here

abstract class LocalTaxTypeBase in Commerce Core 8.2

Provides the base class for local tax types.

Hierarchy

Expanded class hierarchy of LocalTaxTypeBase

File

modules/tax/src/Plugin/Commerce/TaxType/LocalTaxTypeBase.php, line 24

Namespace

Drupal\commerce_tax\Plugin\Commerce\TaxType
View source
abstract class LocalTaxTypeBase extends TaxTypeBase implements LocalTaxTypeInterface {

  /**
   * The rounder.
   *
   * @var \Drupal\commerce_price\RounderInterface
   */
  protected $rounder;

  /**
   * The chain tax rate resolver.
   *
   * @var \Drupal\commerce_tax\Resolver\ChainTaxRateResolverInterface
   */
  protected $chainRateResolver;

  /**
   * The zones.
   *
   * @var \Drupal\commerce_tax\TaxZone[]
   */
  protected $zones;

  /**
   * The matched zones.
   *
   * @var array
   */
  protected $matchedZones;

  /**
   * Constructs a new LocalTaxTypeBase object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Drupal\commerce_price\RounderInterface $rounder
   *   The rounder.
   * @param \Drupal\commerce_tax\Resolver\ChainTaxRateResolverInterface $chain_rate_resolver
   *   The chain tax rate resolver.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, RounderInterface $rounder, ChainTaxRateResolverInterface $chain_rate_resolver) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $event_dispatcher);
    $this->rounder = $rounder;
    $this->chainRateResolver = $chain_rate_resolver;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('entity_type.manager'), $container
      ->get('event_dispatcher'), $container
      ->get('commerce_price.rounder'), $container
      ->get('commerce_tax.chain_tax_rate_resolver'));
  }

  /**
   * {@inheritdoc}
   */
  public function shouldRound() {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(OrderInterface $order) {
    $store = $order
      ->getStore();
    return $this
      ->matchesAddress($store) || $this
      ->matchesRegistrations($store);
  }

  /**
   * {@inheritdoc}
   */
  public function apply(OrderInterface $order) {
    $calculation_date = $order
      ->getCalculationDate();
    $store = $order
      ->getStore();
    $prices_include_tax = $store
      ->get('prices_include_tax')->value;
    $zones = $this
      ->getZones();
    foreach ($order
      ->getItems() as $order_item) {
      $customer_profile = $this
        ->resolveCustomerProfile($order_item);
      if (!$customer_profile) {
        continue;
      }
      $rates = $this
        ->resolveRates($order_item, $customer_profile);
      foreach ($rates as $zone_id => $rate) {
        $zone = $zones[$zone_id];
        $percentage = $rate
          ->getPercentage($calculation_date);

        // Stores are allowed to enter prices without tax even if they're
        // going to be displayed with tax, and vice-versa.
        // Now that the rates are known, use them to determine the final
        // unit price (which will in turn finalize the order item total).
        if ($prices_include_tax != $this
          ->isDisplayInclusive()) {
          $unit_price = $order_item
            ->getUnitPrice();
          $tax_amount = $percentage
            ->calculateTaxAmount($unit_price, $prices_include_tax);
          $tax_amount = $this->rounder
            ->round($tax_amount);
          if ($prices_include_tax && !$this
            ->isDisplayInclusive()) {
            $unit_price = $unit_price
              ->subtract($tax_amount);
          }
          elseif (!$prices_include_tax && $this
            ->isDisplayInclusive()) {
            $unit_price = $unit_price
              ->add($tax_amount);
          }
          $order_item
            ->setUnitPrice($unit_price);
        }

        // Now determine the tax amount, taking into account other adjustments.
        $adjusted_total_price = $order_item
          ->getAdjustedTotalPrice([
          'promotion',
          'fee',
        ]);
        $tax_amount = $percentage
          ->calculateTaxAmount($adjusted_total_price, $this
          ->isDisplayInclusive());
        if ($this
          ->shouldRound()) {
          $tax_amount = $this->rounder
            ->round($tax_amount);
        }
        $order_item
          ->addAdjustment(new Adjustment([
          'type' => 'tax',
          'label' => $zone
            ->getDisplayLabel(),
          'amount' => $tax_amount,
          'percentage' => $percentage
            ->getNumber(),
          'source_id' => $this->parentEntity
            ->id() . '|' . $zone
            ->getId() . '|' . $rate
            ->getId(),
          'included' => $this
            ->isDisplayInclusive(),
        ]));
      }
    }
  }

  /**
   * Checks whether the tax type matches the store's billing address.
   *
   * @param \Drupal\commerce_store\Entity\StoreInterface $store
   *   The store.
   *
   * @return bool
   *   TRUE if the tax type matches the billing address, FALSE otherwise.
   */
  protected function matchesAddress(StoreInterface $store) {
    $zones = $this
      ->getMatchingZones($store
      ->getAddress());
    return !empty($zones);
  }

  /**
   * Checks whether the tax type matches the store's tax registrations.
   *
   * Countries have a yearly transaction threshold (such as $30k) which
   * when breached requires companies to register for tax collection.
   * This also often applies to foreign companies selling to that
   * country's residents. Furthermore, many countries are now trying to
   * make foreign companies collect their tax when selling digital products
   * to their residents, regardless of any threshold.
   * The $store->tax_registrations field allows merchants to precisely specify
   * for which countries they are collecting tax.
   *
   * @param \Drupal\commerce_store\Entity\StoreInterface $store
   *   The store.
   *
   * @return bool
   *   TRUE if the tax type matches the tax registrations, FALSE otherwise.
   */
  protected function matchesRegistrations(StoreInterface $store) {
    foreach ($this
      ->getZones() as $zone) {
      if ($this
        ->checkRegistrations($store, $zone)) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Checks whether the store is registered to collect taxes in the given zone.
   *
   * @param \Drupal\commerce_store\Entity\StoreInterface $store
   *   The store.
   * @param \Drupal\commerce_tax\TaxZone $zone
   *   The tax zone.
   *
   * @return bool
   *   TRUE if the store is registered in the given zone, FALSE otherwise.
   */
  protected function checkRegistrations(StoreInterface $store, TaxZone $zone) {
    foreach ($store
      ->get('tax_registrations') as $field_item) {
      if ($zone
        ->match(new Address($field_item->value))) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Resolves the tax rates for the given order item and customer profile.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
   *   The order item.
   * @param \Drupal\profile\Entity\ProfileInterface $customer_profile
   *   The customer profile. Contains the address and tax number.
   *
   * @return \Drupal\commerce_tax\TaxRate[]
   *   The tax rates, keyed by tax zone ID.
   */
  protected function resolveRates(OrderItemInterface $order_item, ProfileInterface $customer_profile) {
    $zones = $this
      ->resolveZones($order_item, $customer_profile);
    if (!$zones) {
      return [];
    }

    // Provide the tax type entity to the resolvers.
    $this->chainRateResolver
      ->setTaxType($this->parentEntity);
    $rates = [];
    foreach ($zones as $zone) {
      $rate = $this->chainRateResolver
        ->resolve($zone, $order_item, $customer_profile);
      if (is_object($rate)) {
        $rates[$zone
          ->getId()] = $rate;
      }
    }
    return $rates;
  }

  /**
   * Resolves the tax zones for the given order item and customer profile.
   *
   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
   *   The order item.
   * @param \Drupal\profile\Entity\ProfileInterface $customer_profile
   *   The customer profile. Contains the address and tax number.
   *
   * @return \Drupal\commerce_tax\TaxZone[]
   *   The tax zones.
   */
  protected function resolveZones(OrderItemInterface $order_item, ProfileInterface $customer_profile) {
    $customer_address = $customer_profile
      ->get('address')
      ->first();
    $resolved_zones = $this
      ->getMatchingZones($customer_address);
    return $resolved_zones;
  }

  /**
   * Builds the summary of all available tax rates.
   *
   * @return array
   *   The summary form element.
   */
  protected function buildRateSummary() {
    $zones = $this
      ->getZones();
    usort($zones, function ($a, $b) {

      /** @var \Drupal\commerce_tax\TaxZone $a */

      /** @var \Drupal\commerce_tax\TaxZone $b */
      return strcmp($a
        ->getLabel(), $b
        ->getLabel());
    });
    $element = [
      '#type' => 'details',
      '#title' => $this
        ->t('Tax rates'),
      '#markup' => $this
        ->t('The following tax rates are provided:'),
      '#open' => TRUE,
    ];
    $element['table'] = [
      '#type' => 'table',
      '#header' => [
        $this
          ->t('Tax rate'),
        [
          'data' => $this
            ->t('Percentage'),
          'colspan' => 2,
        ],
      ],
      '#input' => FALSE,
    ];
    foreach ($zones as $zone) {
      if (count($zones) > 1) {
        $element['table'][$zone
          ->getId()] = [
          '#attributes' => [
            'class' => [
              'region-title',
            ],
            'no_striping' => TRUE,
          ],
          'label' => [
            '#markup' => $zone
              ->getLabel(),
            '#wrapper_attributes' => [
              'colspan' => 3,
            ],
          ],
        ];
      }
      foreach ($zone
        ->getRates() as $rate) {
        $formatted_percentages = array_map(function ($percentage) {

          /** @var \Drupal\commerce_tax\TaxRatePercentage $percentage */
          return $percentage
            ->toString();
        }, $rate
          ->getPercentages());
        $element['table'][$zone
          ->getId() . '|' . $rate
          ->getId()] = [
          'rate' => [
            '#markup' => $rate
              ->getLabel(),
          ],
          'percentages' => [
            '#markup' => implode('<br>', $formatted_percentages),
          ],
        ];
      }
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function getZones() {
    if (empty($this->zones)) {
      $zones = $this
        ->buildZones();

      // Dispatch an event to allow altering the tax zones.
      $event = new BuildZonesEvent($zones, $this);
      $this->eventDispatcher
        ->dispatch(TaxEvents::BUILD_ZONES, $event);
      $this->zones = $event
        ->getZones();
    }
    return $this->zones;
  }

  /**
   * {@inheritdoc}
   */
  public function getMatchingZones(AddressInterface $address) {
    $address_hash = spl_object_hash($address);
    if (!isset($this->matchedZones[$address_hash])) {
      $this->matchedZones[$address_hash] = [];
      foreach ($this
        ->getZones() as $zone) {
        if ($zone
          ->match($address)) {
          $this->matchedZones[$address_hash][] = $zone;
        }
      }
    }
    return $this->matchedZones[$address_hash];
  }

  /**
   * Builds the tax zones.
   *
   * @return \Drupal\commerce_tax\TaxZone[]
   *   The tax zones, keyed by ID.
   */
  protected abstract function buildZones();

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
LocalTaxTypeBase::$chainRateResolver protected property The chain tax rate resolver.
LocalTaxTypeBase::$matchedZones protected property The matched zones.
LocalTaxTypeBase::$rounder protected property The rounder.
LocalTaxTypeBase::$zones protected property The zones.
LocalTaxTypeBase::applies public function Checks whether the tax type applies to the given order. Overrides TaxTypeBase::applies
LocalTaxTypeBase::apply public function Applies the tax type to the given order. Overrides TaxTypeInterface::apply
LocalTaxTypeBase::buildRateSummary protected function Builds the summary of all available tax rates.
LocalTaxTypeBase::buildZones abstract protected function Builds the tax zones. 6
LocalTaxTypeBase::checkRegistrations protected function Checks whether the store is registered to collect taxes in the given zone.
LocalTaxTypeBase::create public static function Creates an instance of the plugin. Overrides TaxTypeBase::create 1
LocalTaxTypeBase::getMatchingZones public function Gets the tax zones which match the given address. Overrides LocalTaxTypeInterface::getMatchingZones
LocalTaxTypeBase::getZones public function Gets the tax zones. Overrides LocalTaxTypeInterface::getZones
LocalTaxTypeBase::matchesAddress protected function Checks whether the tax type matches the store's billing address. 1
LocalTaxTypeBase::matchesRegistrations protected function Checks whether the tax type matches the store's tax registrations. 1
LocalTaxTypeBase::resolveRates protected function Resolves the tax rates for the given order item and customer profile.
LocalTaxTypeBase::resolveZones protected function Resolves the tax zones for the given order item and customer profile. 2
LocalTaxTypeBase::shouldRound public function Gets whether tax should be rounded at the order item level. Overrides LocalTaxTypeInterface::shouldRound 1
LocalTaxTypeBase::__construct public function Constructs a new LocalTaxTypeBase object. Overrides TaxTypeBase::__construct 1
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TaxTypeBase::$entityId Deprecated protected property The ID of the parent config entity.
TaxTypeBase::$entityTypeManager protected property The entity type manager.
TaxTypeBase::$eventDispatcher protected property The event dispatcher.
TaxTypeBase::$parentEntity protected property The parent config entity.
TaxTypeBase::$profiles protected property A cache of prepared customer profiles, keyed by order ID.
TaxTypeBase::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm 6
TaxTypeBase::buildCustomerProfile protected function Builds a customer profile for the given order.
TaxTypeBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies
TaxTypeBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurableInterface::defaultConfiguration 1
TaxTypeBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
TaxTypeBase::getLabel public function Gets the tax type label. Overrides TaxTypeInterface::getLabel
TaxTypeBase::getTaxableType protected function Gets the taxable type for the given order item.
TaxTypeBase::getWeight public function Gets the tax type weight. Overrides TaxTypeInterface::getWeight
TaxTypeBase::isDisplayInclusive public function Gets whether the tax type is display inclusive. Overrides TaxTypeInterface::isDisplayInclusive
TaxTypeBase::resolveCustomerProfile protected function Resolves the customer profile for the given order item.
TaxTypeBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration 1
TaxTypeBase::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm 1
TaxTypeBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm 1
TaxTypeBase::__sleep public function Overrides DependencySerializationTrait::__sleep
TaxTypeBase::__wakeup public function Overrides DependencySerializationTrait::__wakeup