commerce_dibs-8.x-1.0-rc10/src/EasyOrderBuilder.php
src/EasyOrderBuilder.php
<?php
namespace Drupal\commerce_dibs;
use CommerceGuys\Intl\Calculator;
use Drupal\commerce_dibs\Event\BuildOrderEvent;
use Drupal\commerce_dibs\Event\DibsEvents;
use Drupal\commerce_order\Adjustment;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\Entity\OrderItemInterface;
use Drupal\commerce_price\Price;
use Drupal\commerce_price\RounderInterface;
use Drupal\commerce_product\Entity\ProductVariationInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class EasyOrderBuilder {
use StringTranslationTrait;
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcher
*/
protected $eventDispatcher;
/**
* @var \Drupal\commerce_price\RounderInterface
*/
protected $priceRounder;
public function __construct(EventDispatcherInterface $eventDispatcher, RounderInterface $priceRounder) {
$this->eventDispatcher = $eventDispatcher;
$this->priceRounder = $priceRounder;
}
/**
* @param \Drupal\commerce_order\Entity\OrderInterface $order
*
* @return array
*/
public function buildOrder(OrderInterface $order) {
$dibs_order = [
'order' => [
'items' => [],
'amount' => $this->toMinorUnits($order->getTotalPrice()),
'currency' => $order->getTotalPrice()->getCurrencyCode(),
'reference' => $order->id(),
],
];
if ($order->getEmail()) {
$dibs_order['checkout']['consumer']['email'] = $order->getEmail();
}
if ($order->hasField('shipments') && !$order->get('shipments')->isEmpty()) {
$shipments = $order->get('shipments');
/** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
$shipment = $shipments->first()->entity;
$profile = $shipment->getShippingProfile();
$dibs_order['checkout']['consumer']['reference'] = $profile->id();
$dibs_order['checkout']['consumer']['shippingAddress']['addressLine1'] = $profile->address->address_line1;
$dibs_order['checkout']['consumer']['shippingAddress']['addressLine2'] = $profile->address->address_line2;
$dibs_order['checkout']['consumer']['shippingAddress']['postalCode'] = $profile->address->postal_code;
$dibs_order['checkout']['consumer']['shippingAddress']['city'] = $profile->address->locality;
/** @var \CommerceGuys\Addressing\Country\CountryRepository $country_repository */
$country_repository = \Drupal::service('address.country_repository');
$country = $country_repository->get($profile->address->country_code);
$dibs_order['checkout']['consumer']['shippingAddress']['country'] = $country->getThreeLetterCode();
if (!empty($profile->address->organization)) {
$dibs_order['checkout']['consumer']['company']['name'] = $profile->address->organization;
$dibs_order['checkout']['consumer']['company']['contact']['firstName'] = $profile->address->given_name;
$dibs_order['checkout']['consumer']['company']['contact']['lastName'] = $profile->address->family_name;
}
else {
$dibs_order['checkout']['consumer']['privatePerson']['firstName'] = $profile->address->given_name;
$dibs_order['checkout']['consumer']['privatePerson']['lastName'] = $profile->address->family_name;
}
}
foreach ($order->getItems() as $item) {
$dibs_order['order']['items'][] = $this->buildItem($item);
}
// Shipping is handled separately.
// @todo: Shipping promotion must be handled separately as well.
$exclude_adjustment_types = ['shipping', 'promotion'];
$adjustments = $order->collectAdjustments();
$adjustments = $adjustments ? \Drupal::service('commerce_order.adjustment_transformer')->processAdjustments($adjustments) : [];
foreach ($adjustments as $adjustment) {
// Skip included adjustments and the ones we don't handle.
if ($adjustment->isIncluded() ||
in_array($adjustment->getType(), $exclude_adjustment_types)) {
continue;
}
$dibs_order['order']['items'][] = $this->buildAdjustment($adjustment);
}
// Order level promotions should be added here.
$adjustments = $order->getAdjustments(['promotion']);
$adjustments = $adjustments ? \Drupal::service('commerce_order.adjustment_transformer')->processAdjustments($adjustments) : [];
foreach ($adjustments as $adjustment) {
// Skip included adjustments and the ones we don't handle.
if ($adjustment->isIncluded()) {
continue;
}
$dibs_order['order']['items'][] = $this->buildAdjustment($adjustment);
}
if ($order->hasField('shipments') && !$order->get('shipments')->isEmpty()) {
/** @var \Drupal\commerce_shipping\Entity\ShipmentInterface[] $shipments */
$shipments = $order->get('shipments')->referencedEntities();
foreach ($shipments as $shipment) {
$shipment_amount = $shipment->getAmount();
if (empty($shipment_amount)) {
continue;
}
// Check if there are included tax adjustments.
$tax_adjustments = array_filter($shipment->getAdjustments(['tax']), function ($adjustment) {
return $adjustment->isIncluded();
});
$shipping_line = [
'reference' => 'shipping_fee',
'name' => (string) t('Shipping'),
'quantity' => 1,
'unit' => $this->t('Shipment'),
];
// If there are no included tax adjustments, there's no need to split
// the shipment amounts across multiple VAT rates.
if (!$tax_adjustments) {
$shipping_line += [
'unitPrice' => $this->toMinorUnits($shipment->getAmount()),
'netTotalAmount' => $this->toMinorUnits($shipment->getAmount()),
'grossTotalAmount' => $this->toMinorUnits($shipment->getAmount()),
'taxTate' => 0,
'taxAmount' => 0,
];
$dibs_order['order']['items'][] = $shipping_line;
}
elseif (count($tax_adjustments) === 1) {
/** @var Adjustment $tax_adjustment */
$tax_adjustment = reset($tax_adjustments);
$shipping_line += [
'unitPrice' => $this->toMinorUnits($shipment->getAmount()->subtract($tax_adjustment->getAmount())),
'netTotalAmount' => $this->toMinorUnits($shipment->getAmount()->subtract($tax_adjustment->getAmount())),
'grossTotalAmount' => $this->toMinorUnits($shipment->getAmount()),
'taxRate' => (int) Calculator::multiply($tax_adjustment->getPercentage(), '10000'),
'taxAmount' => $this->toMinorUnits($tax_adjustment->getAmount()),
];
$dibs_order['order']['items'][] = $shipping_line;
}
else {
/** @var \Drupal\commerce_dibs\ShipmentPriceSplitterInterface $shipment_price_splitter */
$shipment_price_splitter = \Drupal::service('commerce_dibs.shipment_price_splitter');
$shipment_amounts = $shipment_price_splitter->split($order, $shipment);
foreach ($shipment_amounts as $amounts) {
$dibs_order['order']['items'][] = $shipping_line + [
'unitPrice' => $this->toMinorUnits($amounts['unit_amount']->subtract($amounts['tax_amount'])),
'netTotalAmount' => $this->toMinorUnits($amounts['unit_amount']->subtract($amounts['tax_amount'])),
'grossTotalAmount' => $this->toMinorUnits($amounts['unit_amount']),
'taxRate' => (int) Calculator::multiply($amounts['tax_rate'], '10000'),
'taxAmount' => $this->toMinorUnits($amounts['tax_amount']),
];
}
}
}
}
$event = new BuildOrderEvent($order, $dibs_order);
$this->eventDispatcher->dispatch(DibsEvents::EASY_BUILD_ORDER, $event);
return $event->getDibsOrder();
}
/**
* @param \Drupal\commerce_order\Entity\OrderItemInterface $item
*
* @return array
*/
public function buildItem(OrderItemInterface $item) {
// Fallback to the order item ID.
$reference = $item->id();
$purchased_entity = $item->getPurchasedEntity();
// Send the "SKU" as the "reference" for product variations.
if ($purchased_entity instanceof ProductVariationInterface) {
$reference = $purchased_entity->getSku();
}
$order_line = [
// The reference is limited to 128 characters.
'reference' => mb_substr($reference, 0, 128),
'name' => $item->label(),
'quantity' => (int) $item->getQuantity(),
'unit' => $this->t('Unit'),
'unitPrice' => $this->toMinorUnits($item->getAdjustedUnitPrice(['promotion'])),
'netTotalAmount' => $this->toMinorUnits($item->getAdjustedTotalPrice(['promotion'])),
'grossTotalAmount' => $this->toMinorUnits($item->getAdjustedTotalPrice(['promotion'])),
'taxRate' => 0,
'taxAmount' => 0,
];
// Only pass included tax adjustments (i.e "VAT"), non included tax
// adjustments are passed separately (i.e "Sales tax").
$tax_adjustments = $item->getAdjustments(['tax']);
if ($tax_adjustments && $tax_adjustments[0]->isIncluded()) {
$tax_rate = $tax_adjustments[0]->getPercentage();
$tax_amount = $tax_adjustments[0]->getAmount();
$net_unit_price = $item->getAdjustedUnitPrice(['promotion'])->subtract($tax_amount->divide((int) $item->getQuantity()));
$net_unit_price = $this->priceRounder->round($net_unit_price);
$order_line = array_merge($order_line, [
'taxRate' => $tax_rate ? (string) Calculator::multiply($tax_rate, '10000') : 0,
'taxAmount' => $this->toMinorUnits($tax_adjustments[0]->getAmount()),
'unitPrice' => $this->toMinorUnits($net_unit_price),
'netTotalAmount' => $this->toMinorUnits($item->getAdjustedTotalPrice(['promotion'])->subtract($tax_amount)),
]);
}
return $order_line;
}
/**
* @param \Drupal\commerce_order\Adjustment $adjustment
*
* @return array
*/
public function buildAdjustment(Adjustment $adjustment) {
$order_line = [
// The reference is limited to 64 characters.
'reference' => $adjustment->getSourceId() ? mb_substr($adjustment->getSourceId(), 0, 128) : $adjustment->getType(),
'name' => $adjustment->getLabel(),
'quantity' => 1,
'unit' => $this->t('Unit'),
'taxRate' => 0,
'taxAmount' => 0,
'unitPrice' => $this->toMinorUnits($adjustment->getAmount()),
'grossTotalAmount' => $this->toMinorUnits($adjustment->getAmount()),
'netTotalAmount' => $this->toMinorUnits($adjustment->getAmount()),
];
return $order_line;
}
/**
* Calculates the total for the given adjustments.
*
* @param \Drupal\commerce_order\Adjustment[] $adjustments
* The adjustments.
* @param string[] $adjustment_types
* The adjustment types to include in the calculation.
* Examples: fee, promotion, tax. Defaults to all adjustment types.
* @param bool $skip_included
* Whether to skip included adjustments (Defaults to FALSE).
*
* @return \Drupal\commerce_price\Price|null
* The adjustments total, or NULL if no matching adjustments were found.
*/
protected function getAdjustmentsTotal(array $adjustments, array $adjustment_types = [], $skip_included = FALSE) {
$adjustments_total = NULL;
$matching_adjustments = [];
foreach ($adjustments as $adjustment) {
if ($skip_included && $adjustment->isIncluded()) {
continue;
}
if ($adjustment_types && !in_array($adjustment->getType(), $adjustment_types)) {
continue;
}
$matching_adjustments[] = $adjustment;
}
if ($matching_adjustments) {
$matching_adjustments = \Drupal::service('commerce_order.adjustment_transformer')->processAdjustments($matching_adjustments);
foreach ($matching_adjustments as $adjustment) {
$adjustments_total = $adjustments_total ? $adjustments_total->add($adjustment->getAmount()) : $adjustment->getAmount();
}
}
return $adjustments_total;
}
/**
* Converts the given amount to its minor units.
*
* @todo: To be replaced with
* https://www.drupal.org/project/commerce/issues/3150917 when that gets in.
*
* @param \Drupal\commerce_price\Price $amount
* The amount.
*
* @return int
* The amount in minor units, as an integer.
*/
protected function toMinorUnits(Price $amount) {
return $amount->multiply(100)->getNumber();
}
}
