Newer
Older
TillQliro / Model / QliroOrder / Builder / ValidateOrderBuilder.php
@Jonas Jonsson Jonas Jonsson on 2 Apr 2024 10 KB Initial
<?php
/**
 * Copyright © Qliro AB. All rights reserved.
 * See LICENSE.txt for license details.
 */

namespace Qliro\QliroOne\Model\QliroOrder\Builder;

use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Quote\Api\Data\CartInterface;
use Qliro\QliroOne\Api\Data\QliroOrderItemInterface;
use Qliro\QliroOne\Api\Data\ValidateOrderResponseInterface;
use Qliro\QliroOne\Api\Data\ValidateOrderResponseInterfaceFactory;
use Qliro\QliroOne\Model\Logger\Manager as LogManager;

/**
 * Shipping Methods Builder class
 */
class ValidateOrderBuilder
{
    /**
     * @var \Qliro\QliroOne\Api\Data\ValidateOrderNotificationInterface
     */
    private $validationRequest;

    /**
     * @var \Magento\Quote\Model\Quote
     */
    private $quote;

    /**
     * @var \Qliro\QliroOne\Api\Data\UpdateShippingMethodsResponseInterfaceFactory
     */
    private $validateOrderResponseFactory;

    /**
     * @var \Magento\CatalogInventory\Api\StockRegistryInterface
     */
    private $stockRegistry;

    /**
     * @var \Qliro\QliroOne\Model\QliroOrder\Builder\OrderItemsBuilder
     */
    private $orderItemsBuilder;

    /**
     * @var LogManager
     */
    private $logManager;

    /**
     * Inject dependencies
     *
     * @param \Qliro\QliroOne\Api\Data\ValidateOrderResponseInterfaceFactory $validateOrderResponseFactory
     * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
     * @param \Qliro\QliroOne\Model\QliroOrder\Builder\OrderItemsBuilder $orderItemsBuilder
     * @param \Qliro\QliroOne\Model\Logger\Manager $logManager
     */
    public function __construct(
        ValidateOrderResponseInterfaceFactory $validateOrderResponseFactory,
        StockRegistryInterface $stockRegistry,
        OrderItemsBuilder $orderItemsBuilder,
        LogManager $logManager
    ) {
        $this->validateOrderResponseFactory = $validateOrderResponseFactory;
        $this->stockRegistry = $stockRegistry;
        $this->orderItemsBuilder = $orderItemsBuilder;
        $this->logManager = $logManager;
    }

    /**
     * Set quote for data extraction
     *
     * @param \Magento\Quote\Api\Data\CartInterface $quote
     * @return $this
     */
    public function setQuote(CartInterface $quote)
    {
        $this->quote = $quote;

        return $this;
    }

    /**
     * Set validation request for data extraction
     *
     * @param \Qliro\QliroOne\Api\Data\ValidateOrderNotificationInterface $validationRequest
     * @return $this
     */
    public function setValidationRequest($validationRequest)
    {
        $this->validationRequest = $validationRequest;

        return $this;
    }

    /**
     * @return \Qliro\QliroOne\Api\Data\ValidateOrderResponseInterface
     */
    public function create()
    {
        if (empty($this->quote)) {
            throw new \LogicException('Quote entity is not set.');
        }

        if (empty($this->validationRequest)) {
            throw new \LogicException('QliroOne validation request is not set.');
        }

        /** @var \Qliro\QliroOne\Api\Data\ValidateOrderResponseInterface $container */
        $container = $this->validateOrderResponseFactory->create();

        $allInStock = $this->checkItemsInStock();

        if (!$allInStock) {
            $this->quote = null;
            $this->validationRequest = null;

            return $container->setDeclineReason(ValidateOrderResponseInterface::REASON_OUT_OF_STOCK);
        }

        if (!$this->quote->isVirtual() && !$this->quote->getShippingAddress()->getShippingMethod()) {
            $this->quote = null;
            $this->validationRequest = null;
            $this->logValidateError(
                'create',
                'not a virtual order, invalid shipping method selected',
                ['method' => $this->quote->getShippingAddress()->getShippingMethod()]
            );

            return $container->setDeclineReason(ValidateOrderResponseInterface::REASON_SHIPPING);
        }

        $orderItemsFromQuote = $this->orderItemsBuilder->setQuote($this->quote)->create();

        $allMatch = $this->compareQuoteAndQliroOrderItems(
            $orderItemsFromQuote,
            $this->validationRequest->getOrderItems()
        );

        if (!$allMatch) {
            return $container->setDeclineReason(ValidateOrderResponseInterface::REASON_OTHER);
        }

        $container->setDeclineReason(null);

        $this->quote = null;
        $this->validationRequest = null;

        return $container;
    }

    /**
     * Check if any items are out of stock
     *
     * @return bool
     */
    private function checkItemsInStock()
    {
        /** @var \Magento\Quote\Model\Quote\Item $quoteItem */
        foreach ($this->quote->getAllVisibleItems() as $quoteItem) {
            $stockItem = $this->stockRegistry->getStockItem(
                $quoteItem->getProduct()->getId(),
                $quoteItem->getProduct()->getStore()->getWebsiteId()
            );

            if (!$stockItem->getIsInStock()) {
                $this->logValidateError(
                    'checkItemsInStock',
                    'not enough stock',
                    ['sku' => $quoteItem->getSku()]
                );
                return false;
            }
        }

        return true;
    }

    /**
     * Return true if the quote items and QliroOne order items match
     *
     * @param \Qliro\QliroOne\Api\Data\QliroOrderItemInterface[] $quoteItems
     * @param \Qliro\QliroOne\Api\Data\QliroOrderItemInterface[] $qliroOrderItems
     * @return bool
     */
    private function compareQuoteAndQliroOrderItems($quoteItems, $qliroOrderItems)
    {
        $hashedQuoteItems = [];
        $hashedQliroItems = [];

        $skipTypes = [QliroOrderItemInterface::TYPE_SHIPPING, QliroOrderItemInterface::TYPE_FEE];

        if (!$quoteItems) {
            $this->logValidateError('compareQuoteAndQliroOrderItems','no Cart Items');
            return false;
        }

        // Gather order items converted from quote and hash them for faster search
        foreach ($quoteItems as $quoteItem) {
            if (!in_array($quoteItem->getType(), $skipTypes)) {
                $hashedQuoteItems[$quoteItem->getMerchantReference()] = $quoteItem;
            }
        }

        if (!$qliroOrderItems) {
            $this->logValidateError('compareQuoteAndQliroOrderItems','no Qliro Items');
            return false;
        }

        // Gather order items from QliroOne order and hash them for faster search, then try to see a diff
        foreach ($qliroOrderItems as $qliroOrderItem) {
            if (!in_array($qliroOrderItem->getType(), $skipTypes)) {
                $hash = $qliroOrderItem->getMerchantReference();
                if ($qliroOrderItem->getType() == QliroOrderItemInterface::TYPE_DISCOUNT) {
                    $qliroOrderItem->setPricePerItemExVat(\abs($qliroOrderItem->getPricePerItemExVat()));
                    $qliroOrderItem->setPricePerItemIncVat(\abs($qliroOrderItem->getPricePerItemIncVat()));
                }
                $hashedQliroItems[$hash] = $qliroOrderItem;

                if (!isset($hashedQuoteItems[$hash])) {
                    $this->logValidateError('compareQuoteAndQliroOrderItems','hashedQuoteItems failed');
                    return false;
                }

                if (!$this->compareItems($hashedQuoteItems[$hash], $hashedQliroItems[$hash])) {
                    return false;
                }
            }
        }

        // Try to see a diff between order items converted from quote and from QliroOne order
        foreach ($quoteItems as $quoteItem) {
            if (!in_array($quoteItem->getType(), $skipTypes)) {
                $hash = $quoteItem->getMerchantReference();

                if (!isset($hashedQliroItems[$hash])) {
                    $this->logValidateError('compareQuoteAndQliroOrderItems','$hashedQliroItems failed');
                    return false;
                }

                if (!$this->compareItems($hashedQuoteItems[$hash], $hashedQliroItems[$hash])) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Compare two QliroOne order items
     *
     * @param \Qliro\QliroOne\Api\Data\QliroOrderItemInterface $item1
     * @param \Qliro\QliroOne\Api\Data\QliroOrderItemInterface $item2
     * @return bool
     */
    private function compareItems(QliroOrderItemInterface $item1, QliroOrderItemInterface $item2)
    {
        if ($item1->getPricePerItemExVat() != $item2->getPricePerItemExVat()) {
            $this->logValidateError(
                'compareItems',
                'pricePerItemExVat different',
                ['item1' => $item1->getPricePerItemExVat(), 'item2 => $item2->getPricePerItemExVat()']
            );
            return false;
        }

        if ($item1->getPricePerItemIncVat() != $item2->getPricePerItemIncVat()) {
            $this->logValidateError(
                'compareItems',
                'pricePerItemIncVat different',
                ['item1' => $item1->getPricePerItemIncVat(), 'item2 => $item2->getPricePerItemIncVat()']
            );
            return false;
        }

        if ($item1->getQuantity() != $item2->getQuantity()) {
            $this->logValidateError(
                'compareItems',
                'quantity different',
                ['item1' => $item1->getQuantity(), 'item2 => $item2->getQuantity()']
            );
            return false;
        }

        if ($item1->getType() != $item2->getType()) {
            $this->logValidateError(
                'compareItems',
                'type different',
                ['item1' => $item1->getType(), 'item2 => $item2->getType()']
            );
            return false;
        }

        return true;
    }

    /**
     * @param string $function
     * @param string $reason
     * @param array $details
     */
    private function logValidateError($function, $reason, $details = [])
    {
        $this->logManager->debug(
            'ValidateOrder',
            [
                'extra' => [
                    'function' => $function,
                    'reason' => $reason,
                    'details' => $details,
                ],
            ]
        );
    }
}