diff -Nuar a/vendor/magento/module-sales-rule/Model/Coupon/Quote/UpdateCouponUsages.php b/vendor/magento/module-sales-rule/Model/Coupon/Quote/UpdateCouponUsages.php new file mode 100644 index 00000000000..02da921e032 --- /dev/null +++ b/vendor/magento/module-sales-rule/Model/Coupon/Quote/UpdateCouponUsages.php @@ -0,0 +1,64 @@ +couponUsagePublisher = $couponUsagePublisher; + $this->updateInfoFactory = $updateInfoFactory; + } + + /** + * Executes the current command + * + * @param CartInterface $quote + * @param bool $increment + * @return void + */ + public function execute(CartInterface $quote, bool $increment): void + { + if (!$quote->getAppliedRuleIds()) { + return; + } + + /** @var UpdateInfo $updateInfo */ + $updateInfo = $this->updateInfoFactory->create(); + $updateInfo->setAppliedRuleIds(explode(',', $quote->getAppliedRuleIds())); + $updateInfo->setCouponCode((string)$quote->getCouponCode()); + $updateInfo->setCustomerId((int)$quote->getCustomerId()); + $updateInfo->setIsIncrement($increment); + + $this->couponUsagePublisher->publish($updateInfo); + } +} diff -Nuar a/vendor/magento/module-sales-rule/Model/Coupon/UpdateCouponUsages.php b/vendor/magento/module-sales-rule/Model/Coupon/UpdateCouponUsages.php index 3236c80e1b7..1645f205d1e 100644 --- a/vendor/magento/module-sales-rule/Model/Coupon/UpdateCouponUsages.php +++ b/vendor/magento/module-sales-rule/Model/Coupon/UpdateCouponUsages.php @@ -8,56 +8,39 @@ declare(strict_types=1); namespace Magento\SalesRule\Model\Coupon; use Magento\Sales\Api\Data\OrderInterface; -use Magento\SalesRule\Model\Coupon; -use Magento\SalesRule\Model\ResourceModel\Coupon\Usage; -use Magento\SalesRule\Model\Rule\CustomerFactory; -use Magento\SalesRule\Model\RuleFactory; +use Magento\SalesRule\Model\Coupon\Usage\Processor as CouponUsageProcessor; +use Magento\SalesRule\Model\Coupon\Usage\UpdateInfo; +use Magento\SalesRule\Model\Coupon\Usage\UpdateInfoFactory; /** - * Updates the coupon usages. + * Updates the coupon usages */ class UpdateCouponUsages { /** - * @var RuleFactory + * @var CouponUsageProcessor */ - private $ruleFactory; + private $couponUsageProcessor; /** - * @var RuleFactory + * @var UpdateInfoFactory */ - private $ruleCustomerFactory; + private $updateInfoFactory; /** - * @var Coupon - */ - private $coupon; - - /** - * @var Usage - */ - private $couponUsage; - - /** - * @param RuleFactory $ruleFactory - * @param CustomerFactory $ruleCustomerFactory - * @param Coupon $coupon - * @param Usage $couponUsage + * @param CouponUsageProcessor $couponUsageProcessor + * @param UpdateInfoFactory $updateInfoFactory */ public function __construct( - RuleFactory $ruleFactory, - CustomerFactory $ruleCustomerFactory, - Coupon $coupon, - Usage $couponUsage + CouponUsageProcessor $couponUsageProcessor, + UpdateInfoFactory $updateInfoFactory ) { - $this->ruleFactory = $ruleFactory; - $this->ruleCustomerFactory = $ruleCustomerFactory; - $this->coupon = $coupon; - $this->couponUsage = $couponUsage; + $this->couponUsageProcessor = $couponUsageProcessor; + $this->updateInfoFactory = $updateInfoFactory; } /** - * Executes the current command. + * Executes the current command * * @param OrderInterface $subject * @param bool $increment @@ -68,86 +51,16 @@ class UpdateCouponUsages if (!$subject || !$subject->getAppliedRuleIds()) { return $subject; } - // lookup rule ids - $ruleIds = explode(',', $subject->getAppliedRuleIds()); - $ruleIds = array_unique($ruleIds); - $customerId = (int)$subject->getCustomerId(); - // use each rule (and apply to customer, if applicable) - foreach ($ruleIds as $ruleId) { - if (!$ruleId) { - continue; - } - $this->updateRuleUsages($increment, (int)$ruleId, $customerId); - } - $this->updateCouponUsages($subject, $increment, $customerId); - - return $subject; - } - /** - * Update the number of rule usages. - * - * @param bool $increment - * @param int $ruleId - * @param int $customerId - */ - private function updateRuleUsages(bool $increment, int $ruleId, int $customerId) - { - /** @var \Magento\SalesRule\Model\Rule $rule */ - $rule = $this->ruleFactory->create(); - $rule->load($ruleId); - if ($rule->getId()) { - $rule->loadCouponCode(); - if ($increment || $rule->getTimesUsed() > 0) { - $rule->setTimesUsed($rule->getTimesUsed() + ($increment ? 1 : -1)); - $rule->save(); - } - if ($customerId) { - $this->updateCustomerRuleUsages($increment, $ruleId, $customerId); - } - } - } + /** @var UpdateInfo $updateInfo */ + $updateInfo = $this->updateInfoFactory->create(); + $updateInfo->setAppliedRuleIds(explode(',', $subject->getAppliedRuleIds())); + $updateInfo->setCouponCode((string)$subject->getCouponCode()); + $updateInfo->setCustomerId((int)$subject->getCustomerId()); + $updateInfo->setIsIncrement($increment); - /** - * Update the number of rule usages per customer. - * - * @param bool $increment - * @param int $ruleId - * @param int $customerId - */ - private function updateCustomerRuleUsages(bool $increment, int $ruleId, int $customerId): void - { - /** @var \Magento\SalesRule\Model\Rule\Customer $ruleCustomer */ - $ruleCustomer = $this->ruleCustomerFactory->create(); - $ruleCustomer->loadByCustomerRule($customerId, $ruleId); - if ($ruleCustomer->getId()) { - if ($increment || $ruleCustomer->getTimesUsed() > 0) { - $ruleCustomer->setTimesUsed($ruleCustomer->getTimesUsed() + ($increment ? 1 : -1)); - } - } elseif ($increment) { - $ruleCustomer->setCustomerId($customerId)->setRuleId($ruleId)->setTimesUsed(1); - } - $ruleCustomer->save(); - } + $this->couponUsageProcessor->process($updateInfo); - /** - * Update the number of coupon usages. - * - * @param OrderInterface $subject - * @param bool $increment - * @param int $customerId - */ - private function updateCouponUsages(OrderInterface $subject, bool $increment, int $customerId): void - { - $this->coupon->load($subject->getCouponCode(), 'code'); - if ($this->coupon->getId()) { - if ($increment || $this->coupon->getTimesUsed() > 0) { - $this->coupon->setTimesUsed($this->coupon->getTimesUsed() + ($increment ? 1 : -1)); - $this->coupon->save(); - } - if ($customerId) { - $this->couponUsage->updateCustomerCouponTimesUsed($customerId, $this->coupon->getId(), $increment); - } - } + return $subject; } } diff -Nuar a/vendor/magento/module-sales-rule/Model/Coupon/Usage/Processor.php b/vendor/magento/module-sales-rule/Model/Coupon/Usage/Processor.php new file mode 100644 index 00000000000..90a456d5ff8 --- /dev/null +++ b/vendor/magento/module-sales-rule/Model/Coupon/Usage/Processor.php @@ -0,0 +1,149 @@ +ruleFactory = $ruleFactory; + $this->ruleCustomerFactory = $ruleCustomerFactory; + $this->coupon = $coupon; + $this->couponUsage = $couponUsage; + } + + /** + * Update coupon usage + * + * @param UpdateInfo $updateInfo + */ + public function process(UpdateInfo $updateInfo): void + { + if (empty($updateInfo->getAppliedRuleIds())) { + return; + } + + if (!empty($updateInfo->getCouponCode())) { + $this->updateCouponUsages($updateInfo); + } + $isIncrement = $updateInfo->isIncrement(); + $customerId = $updateInfo->getCustomerId(); + // use each rule (and apply to customer, if applicable) + foreach (array_unique($updateInfo->getAppliedRuleIds()) as $ruleId) { + if (!(int)$ruleId) { + continue; + } + $this->updateRuleUsages($isIncrement, (int)$ruleId); + if ($customerId) { + $this->updateCustomerRuleUsages($isIncrement, (int)$ruleId, $customerId); + } + } + } + + /** + * Update the number of coupon usages + * + * @param UpdateInfo $updateInfo + */ + private function updateCouponUsages(UpdateInfo $updateInfo): void + { + $isIncrement = $updateInfo->isIncrement(); + $this->coupon->load($updateInfo->getCouponCode(), 'code'); + if ($this->coupon->getId()) { + if ($updateInfo->isIncrement() || $this->coupon->getTimesUsed() > 0) { + $this->coupon->setTimesUsed($this->coupon->getTimesUsed() + ($isIncrement ? 1 : -1)); + $this->coupon->save(); + } + if ($updateInfo->getCustomerId()) { + $this->couponUsage->updateCustomerCouponTimesUsed( + $updateInfo->getCustomerId(), + $this->coupon->getId(), + $isIncrement + ); + } + } + } + + /** + * Update the number of rule usages + * + * @param bool $isIncrement + * @param int $ruleId + */ + private function updateRuleUsages(bool $isIncrement, int $ruleId): void + { + $rule = $this->ruleFactory->create(); + $rule->load($ruleId); + if ($rule->getId()) { + $rule->loadCouponCode(); + if ($isIncrement || $rule->getTimesUsed() > 0) { + $rule->setTimesUsed($rule->getTimesUsed() + ($isIncrement ? 1 : -1)); + $rule->save(); + } + } + } + + /** + * Update the number of rule usages per customer + * + * @param bool $isIncrement + * @param int $ruleId + * @param int $customerId + */ + private function updateCustomerRuleUsages(bool $isIncrement, int $ruleId, int $customerId): void + { + $ruleCustomer = $this->ruleCustomerFactory->create(); + $ruleCustomer->loadByCustomerRule($customerId, $ruleId); + if ($ruleCustomer->getId()) { + if ($isIncrement || $ruleCustomer->getTimesUsed() > 0) { + $ruleCustomer->setTimesUsed($ruleCustomer->getTimesUsed() + ($isIncrement ? 1 : -1)); + } + } elseif ($isIncrement) { + $ruleCustomer->setCustomerId($customerId)->setRuleId($ruleId)->setTimesUsed(1); + } + $ruleCustomer->save(); + } +} diff -Nuar a/vendor/magento/module-sales-rule/Model/Coupon/Usage/UpdateInfo.php b/vendor/magento/module-sales-rule/Model/Coupon/Usage/UpdateInfo.php new file mode 100644 index 00000000000..328093ca1af --- /dev/null +++ b/vendor/magento/module-sales-rule/Model/Coupon/Usage/UpdateInfo.php @@ -0,0 +1,107 @@ +getData(self::APPLIED_RULE_IDS_KEY); + } + + /** + * Set applied rule ids + * + * @param array $value + * @return void + */ + public function setAppliedRuleIds(array $value): void + { + $this->setData(self::APPLIED_RULE_IDS_KEY, $value); + } + + /** + * Get coupon code + * + * @return string + */ + public function getCouponCode(): string + { + return (string)$this->getData(self::COUPON_CODE_KEY); + } + + /** + * Set coupon code + * + * @param string $value + * @return void + */ + public function setCouponCode(string $value): void + { + $this->setData(self::COUPON_CODE_KEY, $value); + } + + /** + * Get customer id + * + * @return int|null + */ + public function getCustomerId(): ?int + { + return $this->getData(self::CUSTOMER_ID_KEY) !== null + ? (int) $this->getData(self::CUSTOMER_ID_KEY) + : null; + } + + /** + * Set customer id + * + * @param int|null $value + * @return void + */ + public function setCustomerId(?int $value): void + { + $this->setData(self::CUSTOMER_ID_KEY, $value); + } + + /** + * Get update mode: increment - true, decrement - false + * + * @return bool + */ + public function isIncrement(): bool + { + return (bool)$this->getData(self::IS_INCREMENT_KEY); + } + + /** + * Set update mode: increment - true, decrement - false + * + * @param bool $value + * @return void + */ + public function setIsIncrement(bool $value): void + { + $this->setData(self::IS_INCREMENT_KEY, $value); + } +} diff -Nuar a/vendor/magento/module-sales-rule/Model/CouponUsageConsumer.php b/vendor/magento/module-sales-rule/Model/CouponUsageConsumer.php new file mode 100644 index 00000000000..0520cb658e4 --- /dev/null +++ b/vendor/magento/module-sales-rule/Model/CouponUsageConsumer.php @@ -0,0 +1,102 @@ +updateInfoFactory = $updateInfoFactory; + $this->processor = $processor; + $this->logger = $logger; + $this->serializer = $serializer; + $this->entityManager = $entityManager; + } + + /** + * Process coupon usage update + * + * @param OperationInterface $operation + * @return void + * @throws \Exception + */ + public function process(OperationInterface $operation): void + { + try { + $serializedData = $operation->getSerializedData(); + $data = $this->serializer->unserialize($serializedData); + $updateInfo = $this->updateInfoFactory->create(); + $updateInfo->setData($data); + $this->processor->process($updateInfo); + } catch (NotFoundException $e) { + $this->logger->critical($e->getMessage()); + $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $errorCode = $e->getCode(); + $message = $e->getMessage(); + } catch (\Exception $e) { + $this->logger->critical($e->getMessage()); + $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $errorCode = $e->getCode(); + $message = __('Sorry, something went wrong during rule usage update. Please see log for details.'); + } + + $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE) + ->setErrorCode($errorCode ?? null) + ->setResultMessage($message ?? null); + + $this->entityManager->save($operation); + } +} diff -Nuar a/vendor/magento/module-sales-rule/Model/Service/CouponUsagePublisher.php b/vendor/magento/module-sales-rule/Model/Service/CouponUsagePublisher.php new file mode 100644 index 00000000000..1d1bbb1f63e --- /dev/null +++ b/vendor/magento/module-sales-rule/Model/Service/CouponUsagePublisher.php @@ -0,0 +1,99 @@ +bulkManagement = $bulkManagement; + $this->operationFactory = $operartionFactory; + $this->identityService = $identityService; + $this->serializer = $serializer; + $this->userContext = $userContext; + } + + /** + * Publish sales rule usage info into the queue + * + * @param string $updateInfo + * @return boolean + */ + public function publish(UpdateInfo $updateInfo): bool + { + $bulkUuid = $this->identityService->generateId(); + $bulkDescription = __('Rule processing: %1', implode(',', $updateInfo->getAppliedRuleIds())); + + $data = [ + 'data' => [ + 'bulk_uuid' => $bulkUuid, + 'topic_name' => self::TOPIC_NAME, + 'serialized_data' => $this->serializer->serialize($updateInfo->getData()), + 'status' => OperationInterface::STATUS_TYPE_OPEN, + ] + ]; + $operation = $this->operationFactory->create($data); + + return $this->bulkManagement->scheduleBulk( + $bulkUuid, + [$operation], + $bulkDescription, + $this->userContext->getUserId() + ); + } +} diff -Nuar a/vendor/magento/module-sales-rule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php b/vendor/magento/module-sales-rule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php index 2d771e4560f..1d416fbcf4f 100644 --- a/vendor/magento/module-sales-rule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php +++ b/vendor/magento/module-sales-rule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php @@ -45,9 +45,10 @@ class AssignCouponDataAfterOrderCustomerAssignObserver implements ObserverInterf $event = $observer->getEvent(); /** @var OrderInterface $order */ $order = $event->getData(self::EVENT_KEY_ORDER); - - if ($order->getCustomerId()) { - $this->updateCouponUsages->execute($order, true); + if (!$order->getCustomerId()) { + return; } + + $this->updateCouponUsages->execute($order, true); } } diff -Nuar a/vendor/magento/module-sales-rule/Observer/CouponUsagesDecrement.php b/vendor/magento/module-sales-rule/Observer/CouponUsagesDecrement.php new file mode 100644 index 00000000000..d0c71994058 --- /dev/null +++ b/vendor/magento/module-sales-rule/Observer/CouponUsagesDecrement.php @@ -0,0 +1,42 @@ +updateCouponUsages = $updateCouponUsages; + } + + /** + * @inheritdoc + */ + public function execute(EventObserver $observer) + { + /** @var CartInterface $quote */ + $quote = $observer->getQuote(); + $this->updateCouponUsages->execute($quote, false); + } +} diff -Nuar a/vendor/magento/module-sales-rule/Plugin/CouponUsagesDecrement.php b/vendor/magento/module-sales-rule/Plugin/CouponUsagesDecrement.php index 87a7c2ed1bd..3be801a2884 100644 --- a/vendor/magento/module-sales-rule/Plugin/CouponUsagesDecrement.php +++ b/vendor/magento/module-sales-rule/Plugin/CouponUsagesDecrement.php @@ -49,11 +49,13 @@ class CouponUsagesDecrement */ public function afterCancel(OrderService $subject, bool $result, $orderId): bool { - $order = $this->orderRepository->get($orderId); - if ($result) { - $this->updateCouponUsages->execute($order, false); + if (!$result) { + return $result; } + $order = $this->orderRepository->get($orderId); + $this->updateCouponUsages->execute($order, false); + return $result; } } diff -Nuar a/vendor/magento/module-sales-rule/Plugin/CouponUsagesIncrement.php b/vendor/magento/module-sales-rule/Plugin/CouponUsagesIncrement.php index 14bbb5fce02..66a32f37eee 100644 --- a/vendor/magento/module-sales-rule/Plugin/CouponUsagesIncrement.php +++ b/vendor/magento/module-sales-rule/Plugin/CouponUsagesIncrement.php @@ -7,12 +7,13 @@ declare(strict_types=1); namespace Magento\SalesRule\Plugin; -use Magento\Sales\Api\Data\OrderInterface; -use Magento\Sales\Model\Service\OrderService; -use Magento\SalesRule\Model\Coupon\UpdateCouponUsages; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteManagement; +use Magento\SalesRule\Model\Coupon\Quote\UpdateCouponUsages; /** - * Increments number of coupon usages after placing order. + * Increments number of coupon usages before placing order */ class CouponUsagesIncrement { @@ -24,24 +25,28 @@ class CouponUsagesIncrement /** * @param UpdateCouponUsages $updateCouponUsages */ - public function __construct( - UpdateCouponUsages $updateCouponUsages - ) { + public function __construct(UpdateCouponUsages $updateCouponUsages) + { $this->updateCouponUsages = $updateCouponUsages; } /** - * Increments number of coupon usages after placing order. + * Increments number of coupon usages before placing order * - * @param OrderService $subject - * @param OrderInterface $result - * @return OrderInterface + * @param QuoteManagement $subject + * @param Quote $quote + * @param array $orderData + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws NoSuchEntityException */ - public function afterPlace(OrderService $subject, OrderInterface $result): OrderInterface + public function beforeSubmit(QuoteManagement $subject, Quote $quote, $orderData = []) { - $this->updateCouponUsages->execute($result, true); + /* if coupon code has been canceled then need to notify the customer */ + if (!$quote->getCouponCode() && $quote->dataHasChangedFor('coupon_code')) { + throw new NoSuchEntityException(__("The coupon code isn't valid. Verify the code and try again.")); + } - return $result; + $this->updateCouponUsages->execute($quote, true); } } diff -Nuar a/vendor/magento/module-sales-rule/etc/communication.xml b/vendor/magento/module-sales-rule/etc/communication.xml index 4c905fa83e2..786e866f0e3 100644 --- a/vendor/magento/module-sales-rule/etc/communication.xml +++ b/vendor/magento/module-sales-rule/etc/communication.xml @@ -9,4 +9,7 @@ + + + diff -Nuar a/vendor/magento/module-sales-rule/etc/di.xml b/vendor/magento/module-sales-rule/etc/di.xml index 6333ae1c644..9e0d6caffb8 100644 --- a/vendor/magento/module-sales-rule/etc/di.xml +++ b/vendor/magento/module-sales-rule/etc/di.xml @@ -189,6 +189,8 @@ + + + + + diff -Nuar a/vendor/magento/module-sales-rule/etc/queue.xml b/vendor/magento/module-sales-rule/etc/queue.xml index 8217a0b9f6c..87dce71b530 100644 --- a/vendor/magento/module-sales-rule/etc/queue.xml +++ b/vendor/magento/module-sales-rule/etc/queue.xml @@ -9,4 +9,7 @@ + + + diff -Nuar a/vendor/magento/module-sales-rule/etc/queue_consumer.xml b/vendor/magento/module-sales-rule/etc/queue_consumer.xml index 9eb585f48e8..bcebaf6a543 100644 --- a/vendor/magento/module-sales-rule/etc/queue_consumer.xml +++ b/vendor/magento/module-sales-rule/etc/queue_consumer.xml @@ -7,4 +7,5 @@ --> + diff -Nuar a/vendor/magento/module-sales-rule/etc/queue_publisher.xml b/vendor/magento/module-sales-rule/etc/queue_publisher.xml index 0863fba2307..f1b8bddf2c0 100644 --- a/vendor/magento/module-sales-rule/etc/queue_publisher.xml +++ b/vendor/magento/module-sales-rule/etc/queue_publisher.xml @@ -9,4 +9,7 @@ + + + diff -Nuar a/vendor/magento/module-sales-rule/etc/queue_topology.xml b/vendor/magento/module-sales-rule/etc/queue_topology.xml index fd6a9bf3672..3902c8a3ab3 100644 --- a/vendor/magento/module-sales-rule/etc/queue_topology.xml +++ b/vendor/magento/module-sales-rule/etc/queue_topology.xml @@ -8,5 +8,6 @@ + diff -Nuar a/vendor/magento/module-sales-rule/view/frontend/requirejs-config.js b/vendor/magento/module-sales-rule/view/frontend/requirejs-config.js index 13b701c6fe6..628d87ec779 100644 --- a/vendor/magento/module-sales-rule/view/frontend/requirejs-config.js +++ b/vendor/magento/module-sales-rule/view/frontend/requirejs-config.js @@ -8,6 +8,9 @@ var config = { mixins: { 'Magento_Checkout/js/action/select-payment-method': { 'Magento_SalesRule/js/action/select-payment-method-mixin': true + }, + 'Magento_Checkout/js/action/place-order': { + 'Magento_SalesRule/js/model/place-order-mixin': true } } } diff -Nuar a/vendor/magento/module-sales-rule/view/frontend/web/js/model/place-order-mixin.js b/vendor/magento/module-sales-rule/view/frontend/web/js/model/place-order-mixin.js new file mode 100644 index 00000000000..da4de3fa19c --- /dev/null +++ b/vendor/magento/module-sales-rule/view/frontend/web/js/model/place-order-mixin.js @@ -0,0 +1,42 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/utils/wrapper', + 'Magento_Checkout/js/model/quote', + 'Magento_SalesRule/js/model/coupon', + 'Magento_Checkout/js/action/get-totals' +], function ($, wrapper, quote, coupon, getTotalsAction) { + 'use strict'; + + return function (placeOrderAction) { + return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, messageContainer) { + var result; + + $.when( + result = originalAction(paymentData, messageContainer) + ).fail( + function () { + var deferred = $.Deferred(), + + /** + * Update coupon form + */ + updateCouponCallback = function () { + if (quote.totals() && !quote.totals()['coupon_code']) { + coupon.setCouponCode(''); + coupon.setIsApplied(false); + } + }; + + getTotalsAction([], deferred); + $.when(deferred).done(updateCouponCallback); + } + ); + + return result; + }); + }; +});