custom/plugins/MagmodulesWebshopnl/src/Api/Controller/OrderProcess.php line 570

  1. <?php
  2. declare(strict_types=1);
  3. namespace MagmodulesWebshopnl\Api\Controller;
  4. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  5. use MagmodulesWebshopnl\Core\Content\WebShopOrder\WebShopOrderEntity;
  6. use MagmodulesWebshopnl\Service\WebShopLogger;
  7. use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryStates;
  8. use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates;
  9. use Shopware\Core\Checkout\Order\OrderStates;
  10. use Shopware\Core\Content\Product\ProductEntity;
  11. use Shopware\Core\Defaults;
  12. use Shopware\Core\Framework\Context;
  13. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  18. use Shopware\Core\Framework\Util\Random;
  19. use Shopware\Core\Framework\Uuid\Uuid;
  20. use Shopware\Core\System\Currency\CurrencyEntity;
  21. use Shopware\Core\System\NumberRange\NumberRangeEntity;
  22. use Shopware\Core\System\SalesChannel\SalesChannelEntity;
  23. use Shopware\Core\System\SystemConfig\SystemConfigService;
  24. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  25. use Symfony\Component\HttpFoundation\JsonResponse;
  26. class OrderProcess extends AbstractController
  27. {
  28.     public function __construct(
  29.         private SystemConfigService $systemConfigService,
  30.         private WebShopLogger $webShopLogger,
  31.         private EntityRepository $orderRepository,
  32.         private EntityRepository $salesChannelRepository,
  33.         private EntityRepository $languageRepository,
  34.         private EntityRepository $stateMachineRepository,
  35.         private EntityRepository $stateMachineStateRepository,
  36.         private EntityRepository $customerRepository,
  37.         private EntityRepository $paymentRepository,
  38.         private EntityRepository $salutationRepository,
  39.         private EntityRepository $numberRangeRepository,
  40.         private EntityRepository $shippingMethodRepository,
  41.         private EntityRepository $numberRangeStateRepository,
  42.         private EntityRepository $webshopnlOrderRepository,
  43.         private EntityRepository $productRepository,
  44.         private EntityRepository $countryRepository,
  45.         private EntityRepository $currencyRepository,
  46.     ) {
  47.     }
  48.     public function processOrder(array $orderstring $salesChannelIdContext $context): array
  49.     {
  50.         if ($this->getCheckExistOrder($order$context) > 0) {
  51.             $logdata $this->getLoggerData($order"Rejected""Order already imported");
  52.             $log $this->systemConfigService->get('MagmodulesWebshopnl.settings.debug'$salesChannelId);
  53.             if ($log) {
  54.                 $this->webShopLogger->addOrderLog($context$logdata);
  55.             }
  56.             return $logdata;
  57.         }
  58.         return $this->orderDataCreate($order$salesChannelId$context);
  59.     }
  60.     private function getCheckExistOrder(array $orderContext $context): int
  61.     {
  62.         return $this->webshopnlOrderRepository->searchIds(
  63.             (new Criteria())->addFilter(new EqualsFilter('webShopOrderId'$order['order_id'])),
  64.             $context
  65.         )->getTotal();
  66.     }
  67.     private function getLoggerData(array $orderstring $statusstring $message): array
  68.     {
  69.         return [
  70.             'order_id' => $order['order_id'] ?? null,
  71.             'merchant_id' => $order['merchant_id'] ?? null,
  72.             'status' => $status,
  73.             'message' => $message,
  74.         ];
  75.     }
  76.     private function orderDataCreate(array $orderDatastring $salesChannelIdContext $context): array
  77.     {
  78.         $orderData['billing_address'] = $orderData['billing_address'] ?? $orderData['delivery_address'];
  79.         $orderData['delivery_address'] = $orderData['delivery_address'] ?? $orderData['billing_address'];
  80.         $billingHash md5(json_encode($orderData['billing_address']));
  81.         $shippingHash md5(json_encode($orderData['delivery_address']));
  82.         $salesChannelEntity $this->getSaleChannelDetails($salesChannelId$context);
  83.         $orderId Uuid::randomHex();
  84.         $billingAddress $this->getOrderAddressData(
  85.             $orderId,
  86.             $orderData['billing_address'],
  87.             $salesChannelEntity->getCountryId(),
  88.             $context
  89.         );
  90.         if ($billingHash !== $shippingHash) {
  91.             $shippingAddress $this->getOrderAddressData(
  92.                 $orderId,
  93.                 $orderData['delivery_address'],
  94.                 $salesChannelEntity->getCountryId(),
  95.                 $context
  96.             );
  97.         } else {
  98.             $shippingAddress $billingAddress;
  99.         }
  100.         $orderCustomerId $this->findOrCreateCustomerId(
  101.             $orderData['billing_address']['email'],
  102.             $salesChannelId,
  103.             $context,
  104.             $salesChannelEntity->getLanguageId(),
  105.             $billingAddress,
  106.             $shippingAddress
  107.         );
  108.         /** @var CurrencyEntity $currency */
  109.         $currency $this->getCurrency($orderData['currency'] ?? ''$context)
  110.             ?? $salesChannelEntity->getCurrency();
  111.         $orderNumberRange $this->getOrderNumberRange($context);
  112.         $nextOrderNumber = (string)$orderNumberRange->getStart();
  113.         if ($orderNumberRange->getState() !== null) {
  114.             $nextOrderNumber = (string)($orderNumberRange->getState()->getLastValue() + 1);
  115.         }
  116.         $order = [];
  117.         $order['id'] = $orderId;
  118.         $order['billingAddress'] = $billingAddress;
  119.         $order['shippingAddress'] = $shippingAddress;
  120.         $order['currencyId'] = $currency->getId();
  121.         $order['orderNumber'] = $nextOrderNumber;
  122.         $order['orderCustomer'] = $this->getOrderCustomerData(
  123.             $orderId,
  124.             $orderCustomerId,
  125.             $orderData['billing_address']['email'],
  126.             $billingAddress,
  127.             $context
  128.         );
  129.         $order['languageId'] = $salesChannelEntity->getLanguageId();
  130.         $order['salesChannelId'] = $salesChannelId;
  131.         $order['price'] = $this->getPrice($orderData);
  132.         $order['lineItems'] = $this->getLineItems($orderId$orderData$context);
  133.         $order['orderDateTime'] = (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  134.         $order['customerId'] = $orderCustomerId;
  135.         $order['currencyFactor'] = $currency->getFactor();
  136.         $order['deepLinkCode'] = Random::getBase64UrlString(32);
  137.         $order['transactions'] = $this->getOrderTransactions($orderId$orderData$context);
  138.         $order['deliveries'] = $this->getOrderDeliveries($orderId$shippingAddress['id'], $orderData$context);
  139.         $order['shippingCosts'] = $this->getShippingCost($orderData);
  140.         $order['stateId'] = $this->getInitialStateIdByName(OrderStates::STATE_MACHINE$context);
  141.         $order['orderDeliveryStateId'] = $this->getInitialStateIdByName(OrderDeliveryStates::STATE_MACHINE$context);
  142.         $order['itemRounding'] = $currency->getItemRounding()->jsonSerialize();
  143.         $order['totalRounding'] = $currency->getTotalRounding()->jsonSerialize();
  144.         $log $this->systemConfigService->get('MagmodulesWebshopnl.settings.debug'$salesChannelId);
  145.         try {
  146.             $this->orderRepository->create([$order], $context);
  147.             $this->updateOrderNumber($orderNumberRange, (int)$nextOrderNumber$context);
  148.             $this->addWebShopOrder($order['id'], $orderData$context$salesChannelId);
  149.             if ($log) {
  150.                 $status "Confirmed";
  151.                 $message "Order was imported";
  152.                 $this->webShopLogger->addOrderLog($context$this->getLoggerData($orderData$status$message));
  153.             }
  154.         } catch (UniqueConstraintViolationException $exception) {
  155.             if ($log) {
  156.                 $status "Rejected";
  157.                 $message $exception->getMessage();
  158.                 $this->webShopLogger->addOrderLog($context$this->getLoggerData($orderData$status$message));
  159.             }
  160.         }
  161.         return $order;
  162.     }
  163.     private function addWebShopOrder(string $orderId, array $getOrderContext $contextstring $salesChannelId): void
  164.     {
  165.         $this->webshopnlOrderRepository->create([
  166.             [
  167.                 'id' => Uuid::randomHex(),
  168.                 'salesChannelId' => $salesChannelId,
  169.                 'webShopOrderId' => $getOrder['order_id'],
  170.                 'status' => 'imported',
  171.                 'orderId' => $orderId,
  172.                 'merchantId' => $getOrder['merchant_id'],
  173.             ],
  174.         ], $context);
  175.     }
  176.     private function getOrderDeliveries(
  177.         string $orderId,
  178.         string $shippingAddressId,
  179.         array $orderData,
  180.         Context $context
  181.     ): array {
  182.         $shippingDateEarliest date("Y/m/d");
  183.         $latestDate strtotime("+4 day"strtotime($shippingDateEarliest));
  184.         $shippingDateLatest date('Y/m/d'$latestDate);
  185.         return [
  186.             [
  187.                 'orderId' => $orderId,
  188.                 //'shippingOrderAddressId' => $shippingAddressId,
  189.                 'shippingMethodId' => $this->getShippingMethodId($context),
  190.                 'stateId' => $this->getInitialStateIdByName(OrderDeliveryStates::STATE_MACHINE$context),
  191.                 'trackingCodes' => [],
  192.                 'shippingDateEarliest' => $shippingDateEarliest,
  193.                 'shippingDateLatest' => $shippingDateLatest,
  194.                 'shippingCosts' => $this->getShippingCost($orderData),
  195.             ],
  196.         ];
  197.     }
  198.     private function getShippingMethodId($context)
  199.     {
  200.         $paymentCriteria = new Criteria();
  201.         $paymentCriteria->addFilter(new EqualsFilter('name''webshopnl'));
  202.         return $this->shippingMethodRepository->search($paymentCriteria$context)->first()->getId();
  203.     }
  204.     private function getOrderTransactions(string $orderId, array $orderDataContext $context): array
  205.     {
  206.         if (array_key_exists('paid_at'$orderData) && $orderData['paid_at'] !== null) {
  207.             $stateId $this->getIdByTransactionStateName(OrderTransactionStates::STATE_PAID,
  208.                 $context);
  209.         } else {
  210.             $stateId $this->getInitialStateIdByName(
  211.                 OrderTransactionStates::STATE_MACHINE,
  212.                 $context
  213.             );
  214.         }
  215.         return [
  216.             [
  217.                 'orderId' => $orderId,
  218.                 'paymentMethodId' => $this->getPaymentMethodID($context),
  219.                 'amount' => $this->getTransactionAmount($orderData),
  220.                 'stateId' => $stateId,
  221.             ],
  222.         ];
  223.     }
  224.     private function getPaymentMethodID(Context $context): ?string
  225.     {
  226.         $paymentCriteria = new Criteria();
  227.         $paymentCriteria->addFilter(new EqualsFilter('name''webshopnl'));
  228.         return $this->paymentRepository->searchIds($paymentCriteria$context)->firstId();
  229.     }
  230.     private function getTransactionAmount(array $orderData): array
  231.     {
  232.         $quantity $orderData['items']['quantity'];
  233.         $productIds $orderData['items']['products'];
  234.         foreach ($productIds as $data) {
  235.             $price $data['per_item'];
  236.         }
  237.         return [
  238.             "quantity" => $quantity,
  239.             "taxRules" => [
  240.                 [
  241.                     "taxRate" => 0,
  242.                     "percentage" => 0,
  243.                 ],
  244.             ],
  245.             "listPrice" => null,
  246.             "unitPrice" => $price,
  247.             "totalPrice" => $price $quantity 100,
  248.             "referencePrice" => null,
  249.             "calculatedTaxes" => [
  250.                 [
  251.                     "tax" => 0,
  252.                     "price" => 0,
  253.                     "taxRate" => 0,
  254.                 ],
  255.             ],
  256.             "regulationPrice" => null,
  257.         ];
  258.     }
  259.     private function getLineItems(string $orderId, array $orderDataContext $context): array
  260.     {
  261.         $lineItemReturn = [];
  262.         foreach ($orderData['items']['products'] as $lineItem) {
  263.             $lineItemNew = [
  264.                 'id' => Uuid::randomHex(),
  265.                 'orderId' => $orderId,
  266.                 'payload' => [],
  267.                 'label' => $lineItem['name'],
  268.                 'quantity' => $lineItem['quantity'],
  269.                 'price' => $this->getOrderLineItemPrice($lineItem),
  270.                 'removable' => true,
  271.                 'stackable' => true,
  272.                 'type' => 'product',
  273.                 'identifier' => $lineItem['remote_id'],
  274.             ];
  275.             if ($product $this->getProductEntity($lineItem['remote_id'], $context)) {
  276.                 $lineItemNew['productId'] = $product->getId();
  277.                 $lineItemNew['referencedId'] = $product->getId();
  278.                 $lineItemNew['payload'] = [
  279.                     'stock' => $product->getStock(),
  280.                     'productNumber' => $product->getProductNumber(),
  281.                 ];
  282.             }
  283.             $lineItemReturn[] = $lineItemNew;
  284.         }
  285.         return $lineItemReturn;
  286.     }
  287.     private function getProductEntity($productId$context): ?ProductEntity
  288.     {
  289.         $criteria = new  Criteria();
  290.         $criteria->addFilter(new EqualsFilter('id'$productId));
  291.         return $this->productRepository->search($criteria$context)->first();
  292.     }
  293.     private function getOrderLineItemPrice(array $lineItem): array
  294.     {
  295.         return [
  296.             "quantity" => $lineItem['quantity'],
  297.             "taxRules" => [
  298.                 [
  299.                     "taxRate" => 0,
  300.                     "extensions" => [],
  301.                     "percentage" => 0,
  302.                 ],
  303.             ],
  304.             "listPrice" => null,
  305.             "unitPrice" => $lineItem['per_item'] / 100,
  306.             "totalPrice" => ($lineItem['per_item'] / 100) * $lineItem['quantity'],
  307.             "referencePrice" => null,
  308.             "calculatedTaxes" => [
  309.                 [
  310.                     "tax" => 0,
  311.                     "price" => 0,
  312.                     "taxRate" => 0,
  313.                     "extensions" => [],
  314.                 ],
  315.             ],
  316.         ];
  317.     }
  318.     private function getOrderCustomerData(
  319.         string $orderId,
  320.         $customerId,
  321.         string $email,
  322.         array $billingAddress,
  323.         Context $context
  324.     ): array {
  325.         return [
  326.             'customerId' => $customerId,
  327.             'orderId' => $orderId,
  328.             'email' => $email,
  329.             'salutationId' => $this->getValidSalutationId($context),
  330.             'firstName' => $billingAddress['firstName'],
  331.             'lastName' => $billingAddress['lastName'],
  332.         ];
  333.     }
  334.     private function getShippingCost(array $orderData): array
  335.     {
  336.         return [
  337.             "quantity" => $orderData['items']['quantity'],
  338.             "taxRules" => [
  339.                 [
  340.                     "taxRate" => 0,
  341.                     "extensions" => [],
  342.                     "percentage" => 0,
  343.                 ],
  344.             ],
  345.             "listPrice" => null,
  346.             "unitPrice" => 0,
  347.             "totalPrice" => $orderData['items']['delivery_cost'] / 100,
  348.             "referencePrice" => null,
  349.             "calculatedTaxes" => [
  350.                 [
  351.                     "tax" => 0,
  352.                     "price" => 0,
  353.                     "taxRate" => 0,
  354.                     "extensions" => [],
  355.                 ],
  356.             ],
  357.             "regulationPrice" => null,
  358.         ];
  359.     }
  360.     private function getOrderNumberRange(Context $context): NumberRangeEntity
  361.     {
  362.         $criteria = new  Criteria();
  363.         $criteria->addFilter(new EqualsFilter('type.technicalName''order'));
  364.         $criteria->addAssociation('state');
  365.         return $this->numberRangeRepository->search($criteria$context)->first();
  366.     }
  367.     private function getPrice(array $orderData): array
  368.     {
  369.         return [
  370.             "totalPrice" => $orderData['items']['total'] / 100,
  371.             "netPrice" => $orderData['items']['total'] / 100,
  372.             "rawTotal" => $orderData['items']['total'] / 100,
  373.             "taxRules" => [
  374.                 [
  375.                     "taxRate" => 0,
  376.                     "extensions" => [],
  377.                     "percentage" => 0,
  378.                 ],
  379.             ],
  380.             "taxStatus" => "gross",
  381.             "positionPrice" => $orderData['items']['products_cost'],
  382.             "calculatedTaxes" => [
  383.                 [
  384.                     "tax" => 0,
  385.                     "price" => 0,
  386.                     "taxRate" => 0,
  387.                     "extensions" => [],
  388.                 ],
  389.             ],
  390.         ];
  391.     }
  392.     private function updateOrderNumber(NumberRangeEntity $orderNumberRangeint $newValueContext $context): void
  393.     {
  394.         $numberRange = [
  395.             'id' => $orderNumberRange->getState()?->getId() ?? Uuid::randomHex(),
  396.             'numberRangeId' => $orderNumberRange->getId(),
  397.             'lastValue' => $newValue,
  398.         ];
  399.         $this->numberRangeStateRepository->upsert([$numberRange], $context);
  400.     }
  401.     private function findOrCreateCustomerId(
  402.         string $email,
  403.         string $salesChannelId,
  404.         Context $context,
  405.         string $languageId,
  406.         array $billingAddress,
  407.         array $shippingAddress
  408.     ): string {
  409.         $criteria = new Criteria();
  410.         $criteria->addFilter(new EqualsFilter('email'$email));
  411.         $customerId $this->customerRepository->searchIds($criteria$context)->firstId();
  412.         if ($customerId) {
  413.             return $customerId;
  414.         }
  415.         $customerId Uuid::randomHex();
  416.         $this->customerRepository->create([
  417.             [
  418.                 'id' => $customerId,
  419.                 'groupId' => $this->systemConfigService->get(
  420.                     'MagmodulesWebshopnl.settings.defaultCustomerGroup',
  421.                     $salesChannelId
  422.                 ),
  423.                 'defaultPaymentMethodId' => $this->getValidPaymentMethodId($salesChannelId$context),
  424.                 'salesChannelId' => $salesChannelId,
  425.                 'languageId' => $languageId,
  426.                 'defaultBillingAddress' => $billingAddress,
  427.                 'defaultShippingAddress' => $shippingAddress,
  428.                 'customerNumber' => "10142",
  429.                 'firstName' => $billingAddress['firstName'],
  430.                 'lastName' => $billingAddress['lastName'],
  431.                 'email' => $email,
  432.             ],
  433.         ], $context);
  434.         return $customerId;
  435.     }
  436.     private function getValidPaymentMethodId(?string $salesChannelIdContext $context): string
  437.     {
  438.         $criteria = (new Criteria())
  439.             ->setLimit(1)
  440.             ->addFilter(new EqualsFilter('active'true));
  441.         if ($salesChannelId) {
  442.             $criteria->addFilter(new EqualsFilter('salesChannels.id'$salesChannelId));
  443.         }
  444.         /** @var string $id */
  445.         $id $this->paymentRepository->searchIds($criteria$context)->firstId();
  446.         return $id;
  447.     }
  448.     private function getIdByTransactionStateName(string $nameContext $context): ?string
  449.     {
  450.         $stateMachineCriteria = new Criteria();
  451.         $stateMachineCriteria->addFilter(new EqualsFilter('technicalName'$name));
  452.         return $this->stateMachineStateRepository->search($stateMachineCriteria$context)->first()?->getId();
  453.     }
  454.     private function getInitialStateIdByName(string $name$context)
  455.     {
  456.         $stateMachineCriteria = new Criteria();
  457.         $stateMachineCriteria->addFilter(new EqualsFilter('technicalName'$name));
  458.         return $this->stateMachineRepository->search($stateMachineCriteria$context)->first()->getInitialStateId();
  459.     }
  460.     private function getSaleChannelDetails(string $salesChannelIdContext $context): SalesChannelEntity
  461.     {
  462.         $criteria = new  Criteria();
  463.         $criteria->addAssociation('currency');
  464.         $criteria->addFilter(new EqualsFilter('id'$salesChannelId));
  465.         return $this->salesChannelRepository->search($criteria$context)->first();
  466.     }
  467.     private function getLanguageDetails(Context $context)
  468.     {
  469.         $criteria = new  Criteria();
  470.         $criteria->addFilter(new EqualsFilter('locale.code''en-GB'));
  471.         return $this->languageRepository->search($criteria$context)->first()->getId();
  472.     }
  473.     private function getOrderAddressData(
  474.         string $orderId,
  475.         array $addressData,
  476.         string $fallbackCountryId,
  477.         Context $context
  478.     ): array {
  479.         return [
  480.             'id' => Uuid::randomHex(),
  481.             'countryId' => $this->getCountryIdByCode($addressData['country_code'], $context) ?? $fallbackCountryId,
  482.             'orderId' => $orderId,
  483.             'salutationId' => $this->getValidSalutationId($context),
  484.             'firstName' => $addressData['first_name'],
  485.             'lastName' => $addressData['surname'],
  486.             'street' => $addressData['street_name'],
  487.             'zipcode' => $addressData['zip_code'],
  488.             'city' => $addressData['city'],
  489.             'additionalAddressLine1' => $addressData['extra_address_information'],
  490.             'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
  491.         ];
  492.     }
  493.     private function getValidSalutationId(Context $context): string
  494.     {
  495.         $criteria = (new Criteria())
  496.             ->setLimit(1)
  497.             ->addSorting(new FieldSorting('salutationKey'));
  498.         /** @var string $id */
  499.         $id $this->salutationRepository->searchIds($criteria$context)->firstId();
  500.         return $id;
  501.     }
  502.     public function getOrderStatus(string $orderIdContext $context)
  503.     {
  504.         $getOrderCriteria = new  Criteria();
  505.         $getOrderCriteria->addFilter(new OrFilter([
  506.             new EqualsFilter('orderId'$orderId),
  507.             new EqualsFilter('webShopOrderId'$orderId)
  508.         ]));
  509.         $getOrderCriteria->addAssociation('order.stateMachineState');
  510.         $order $this->webshopnlOrderRepository->search($getOrderCriteria$context)->first();
  511.         if ($order instanceof WebShopOrderEntity && $order->getOrder() !== null) {
  512.             return new JsonResponse([
  513.                 'id' => $orderId,
  514.                 'order_status' => $order->getOrder()->getStateMachineState()?->getTechnicalName() ?? 'error',
  515.             ]);
  516.         }
  517.         return new JsonResponse([
  518.             'status' => "No order found",
  519.         ]);
  520.     }
  521.     private function getCountryIdByCode(string $codeContext $context): ?string
  522.     {
  523.         $criteria = new Criteria();
  524.         $criteria->addFilter(new EqualsFilter('iso'$code));
  525.         return $this->countryRepository->searchIds($criteria$context)->firstId();
  526.     }
  527.     private function getCurrency(string $codeContext $context): ?CurrencyEntity
  528.     {
  529.         if (empty($code)) {
  530.             return null;
  531.         }
  532.         $criteria = new Criteria();
  533.         $criteria->addFilter(new EqualsFilter('isoCode'$code));
  534.         return $this->currencyRepository->search($criteria$context)->first();
  535.     }
  536. }