You are here

public function PriceSplitter::split in Commerce Core 8.2

Splits the given amount across order items.

Parameters

\Drupal\commerce_order\Entity\OrderInterface $order: The order.

\Drupal\commerce_price\Price $amount: The amount.

string $percentage: The percentage used to calculate the amount, as a decimal. For example, '0.2' for 20%. When missing, calculated by comparing the amount to the order subtotal.

Return value

\Drupal\commerce_price\Price[] An array of amounts keyed by order item ID.

Overrides PriceSplitterInterface::split

File

modules/order/src/PriceSplitter.php, line 43

Class

PriceSplitter

Namespace

Drupal\commerce_order

Code

public function split(OrderInterface $order, Price $amount, $percentage = NULL) {
  if (!$order
    ->getItems()) {
    return [];
  }
  if (!$percentage) {

    // The percentage is intentionally not rounded, for maximum precision.
    $percentage = Calculator::divide($amount
      ->getNumber(), $order
      ->getSubtotalPrice()
      ->getNumber());
  }

  // Calculate the initial per-order-item amounts using the percentage.
  // Round down to ensure that their sum isn't larger than the full amount.
  $amounts = [];
  foreach ($order
    ->getItems() as $order_item) {
    if (!$order_item
      ->getTotalPrice()
      ->isZero()) {
      $individual_amount = $order_item
        ->getTotalPrice()
        ->multiply($percentage);
      $individual_amount = $this->rounder
        ->round($individual_amount, PHP_ROUND_HALF_DOWN);

      // Due to rounding it is possible for the last calculated
      // per-order-item amount to be larger than the total remaining amount.
      if ($individual_amount
        ->greaterThan($amount)) {
        $individual_amount = $amount;
      }
      $amounts[$order_item
        ->id()] = $individual_amount;
      $amount = $amount
        ->subtract($individual_amount);
    }
  }

  // The individual amounts don't add up to the full amount, distribute
  // the reminder among them.
  if (!$amount
    ->isZero()) {

    /** @var \Drupal\commerce_price\Entity\CurrencyInterface $currency */
    $currency = $this->currencyStorage
      ->load($amount
      ->getCurrencyCode());
    $precision = $currency
      ->getFractionDigits();

    // Use the smallest rounded currency amount (e.g. '0.01' for USD).
    $smallest_number = Calculator::divide('1', pow(10, $precision), $precision);
    $smallest_amount = new Price($smallest_number, $amount
      ->getCurrencyCode());
    while (!$amount
      ->isZero()) {
      foreach ($amounts as $order_item_id => $individual_amount) {
        $amounts[$order_item_id] = $individual_amount
          ->add($smallest_amount);
        $amount = $amount
          ->subtract($smallest_amount);
        if ($amount
          ->isZero()) {
          break 2;
        }
      }
    }
  }
  return $amounts;
}