magento / magento/inventory

observer "inventory_sales_virtual_source_deduction_processor" destroys checkout for free virtual and downloadable products, when store pickup is active

Open
#3,372 3 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Progress: ready for grooming
Dominant language
PHP
Stars
357
Forks
262
PR merge metrics
No merged PRs in 30d

Description

Preconditions (*)
  1. magento/module-inventory-shipping 1.2.2
  2. Magento Enterprise Edition 2.4.5-p1
Steps to reproduce (*)
  1. Set sales/minimum_order/active to 0
  2. Activate carriers/instore/active
  3. Set carriers/instore/price to 0.00
  4. Create a custom stock and source and activate in-store pickup for it.
  5. Create a product A with a price of $10, assign the source with a qty of 99 to it. Set it to be in stock
  6. Create a virtual product B with a price of $0, assign the source with a qty of 99 to it. Set it to be in stock.
  7. Create two customers A and B
  8. Purchase the product A with the logged in customer A and order it via in-store pickup
  9. Now log out and log in as customer B
  10. Try to purchase the free virtual product B in the checkout.
Expected result (*)
  1. I would expect the order to be successfully placed.
Actual result (*)
  1. After trying to purchase in the checkout, I get the error message No entry with orderID = xxx and the order can not be fulfilled.

We debugged that issue for a while and found out, in the end, that the observer inventory_sales_virtual_source_deduction_processor is the reason for this issue.
The call stack is the following:

- ShippingAssignmentBuilder.php:79, Magento\Sales\Model\Order\ShippingAssignmentBuilder->create()
- OrderRepository.php:306, Magento\Sales\Model\OrderRepository->setShippingAssignments()
- OrderRepository.php:228, Magento\Sales\Model\OrderRepository->getList()
- Interceptor.php:58, Magento\Sales\Model\OrderRepository\Interceptor->___callParent()
- Interceptor.php:138, Magento\Sales\Model\OrderRepository\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- Interceptor.php:153, Magento\Sales\Model\OrderRepository\Interceptor->___callPlugins()
- Interceptor.php:32, Magento\Sales\Model\OrderRepository\Interceptor->getList()
- GetActiveStorePickupOrdersBySource.php:65, Magento\InventoryInStorePickupSales\Model\SourceSelection\GetActiveStorePickupOrdersBySource->execute()
- GetSourceItemQtyAvailableService.php:105, -Magento\InventoryInStorePickupSales\Model\SourceSelection\GetSourceItemQtyAvailableService->getStorePickupOrdersBySourceItem()
- GetSourceItemQtyAvailableService.php:76, Magento\InventoryInStorePickupSales\Model\SourceSelection\GetSourceItemQtyAvailableService->getStorePickupReservedQty()
- GetSourceItemQtyAvailableService.php:64, Magento\InventoryInStorePickupSales\Model\SourceSelection\GetSourceItemQtyAvailableService->execute()
- GetDefaultSortedSourcesResult.php:115, Magento\InventorySourceSelectionApi\Model\Algorithms\Result\GetDefaultSortedSourcesResult->execute()
- PriorityBasedAlgorithm.php:73, Magento\InventorySourceSelection\Model\Algorithms\PriorityBasedAlgorithm->execute()
- SourceSelectionService.php:61, Magento\InventorySourceSelectionApi\Model\SourceSelectionService->execute()
- GetSourceSelectionResultFromInvoice.php:99, Magento\InventoryShipping\Model\GetSourceSelectionResultFromInvoice->execute()
- VirtualSourceDeductionProcessor.php:107, Magento\InventoryShipping\Observer\VirtualSourceDeductionProcessor->execute()
- InvokerDefault.php:88, Magento\Framework\Event\Invoker\InvokerDefault->_callObserverMethod()
- InvokerDefault.php:74, Magento\Framework\Event\Invoker\InvokerDefault->dispatch()
- Manager.php:97, Magento\Staging\Model\Event\Manager->dispatch()
- Proxy.php:95, Magento\Staging\Model\Event\Manager\Proxy->dispatch()
- AbstractModel.php:832, Magento\Framework\Model\AbstractModel->afterSave()
- Interceptor.php:1418, Magento\Sales\Model\Order\Invoice\Interceptor->afterSave()
- AbstractDb.php:56, Magento\Framework\Model\ResourceModel\Db\VersionControl\AbstractDb->processAfterSaves()
- AbstractDb.php:402, Magento\Framework\Model\ResourceModel\Db\AbstractDb->save()
- AbstractModel.php:658, Magento\Framework\Model\AbstractModel->save()
- Interceptor.php:1355, Magento\Sales\Model\Order\Invoice\Interceptor->save()
- Relation.php:94, Magento\Sales\Model\ResourceModel\Order\Relation->processRelation()
- RelationComposite.php:48, Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite->processRelations()
- AbstractDb.php:57, Magento\Framework\Model\ResourceModel\Db\VersionControl\AbstractDb->processAfterSaves()
- AbstractDb.php:402, Magento\Framework\Model\ResourceModel\Db\AbstractDb->save()
- Order.php:180, Magento\Sales\Model\ResourceModel\Order->save()
- Interceptor.php:32, Magento\Sales\Model\ResourceModel\Order\Interceptor->save()
- OrderRepository.php:282, Magento\Sales\Model\OrderRepository->save()
- Interceptor.php:58, Magento\Sales\Model\OrderRepository\Interceptor->___callParent()
- Interceptor.php:138, Magento\Sales\Model\OrderRepository\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- Interceptor.php:153, Magento\Sales\Model\OrderRepository\Interceptor->___callPlugins()
- Interceptor.php:59, Magento\Sales\Model\OrderRepository\Interceptor->save()
- OrderService.php:214, Magento\Sales\Model\Service\OrderService->place()
- Interceptor.php:58, Magento\Sales\Model\Service\OrderService\Interceptor->___callParent()
- Interceptor.php:138, Magento\Sales\Model\Service\OrderService\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- AppendReservationsAfterOrderPlacementPlugin.php:195, Magento\InventorySales\Plugin\Sales\OrderManagement\AppendReservationsAfterOrderPlacementPlugin->aroundPlace()
- Interceptor.php:135, Magento\Sales\Model\Service\OrderService\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- Interceptor.php:153, Magento\Sales\Model\Service\OrderService\Interceptor->___callPlugins()
- Interceptor.php:86, Magento\Sales\Model\Service\OrderService\Interceptor->place()
- QuoteManagement.php:603, Magento\Quote\Model\QuoteManagement->submitQuote()
- QuoteManagement.php:483, Magento\Quote\Model\QuoteManagement->submit()
- Interceptor.php:58, Magento\Quote\Model\QuoteManagement\Interceptor->___callParent()
- Interceptor.php:138, Magento\Quote\Model\QuoteManagement\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- CouponUsagesIncrement.php:54, Magento\SalesRule\Plugin\CouponUsagesIncrement->aroundSubmit()
- Interceptor.php:135, Magento\Quote\Model\QuoteManagement\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- Interceptor.php:153, Magento\Quote\Model\QuoteManagement\Interceptor->___callPlugins()
- Interceptor.php:68, Magento\Quote\Model\QuoteManagement\Interceptor->submit()
- QuoteManagement.php:441, Magento\Quote\Model\QuoteManagement->placeOrder()
- Interceptor.php:50, Magento\Quote\Model\QuoteManagement\Interceptor->placeOrder()
PaymentInformationManagement.php:125, Magento\Checkout\Model\PaymentInformationManagement->savePaymentInformationAndPlaceOrder()
- Interceptor.php:58, Magento\Checkout\Model\PaymentInformationManagement\Interceptor->___callParent()
- Interceptor.php:138, Magento\Checkout\Model\PaymentInformationManagement\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- Interceptor.php:153, Magento\Checkout\Model\PaymentInformationManagement\Interceptor->___callPlugins()
- Interceptor.php:23, Magento\Checkout\Model\PaymentInformationManagement\Interceptor->savePaymentInformationAndPlaceOrder()
- AsyncPaymentInformationCustomerPublisher.php:120, Magento\AsyncOrder\Model\AsyncPaymentInformationCustomerPublisher->savePaymentInformationAndPlaceOrder()
- SynchronousRequestProcessor.php:95, call_user_func_array:{/var/www/html/vendor/magento/module-webapi/Controller/Rest/SynchronousRequestProcessor.php:95}()
- SynchronousRequestProcessor.php:95, Magento\Webapi\Controller\Rest\SynchronousRequestProcessor->process()
- Rest.php:195, Magento\Webapi\Controller\Rest->dispatch()
- Interceptor.php:58, Magento\Webapi\Controller\Rest\Interceptor->___callParent()
- Interceptor.php:138, Magento\Webapi\Controller\Rest\Interceptor->Magento\Framework\Interception\{closure:/var/www/html/vendor/magento/framework/Interception/Interceptor.php:104-151}()
- Interceptor.php:153, Magento\Webapi\Controller\Rest\Interceptor->___callPlugins()
- Interceptor.php:23, Magento\Webapi\Controller\Rest\Interceptor->dispatch()
- Http.php:116, Magento\Framework\App\Http->launch()
- Bootstrap.php:264, Magento\Framework\App\Bootstrap->run()
- index.php:30, {main}()

What happens in the end, is, that the VirtualSourceDeductionProcessor starts to create a list of all orders, which still need to be collected from the in-store pickup location. (see \Magento\InventoryInStorePickupSales\Model\SourceSelection\GetActiveStorePickupOrdersBySource::execute).
It fails in the end, when it starts loading these orders.
The reason for that is, that free orders are invoiced directly during the checkout process.
When the old order A is now loaded, the plugin authorization is calling \Magento\Sales\Model\ResourceModel\Order\Plugin\Authorization::afterLoad, which calls \Magento\Sales\Model\ResourceModel\Order\Plugin\Authorization::isAllowed.
As the user context is still customer in the checkout, this check returns false, as user B is not allowed to load the order of user A.
For virtual products, which cost more than $0, this does not happen, as the invoice process starts later and this way, the check is not run.

Our workaround for this issue is to disable the observer completely, as we are lucky and don't need to care about stock in the virtual products of our project. But in general it would be great to find a better solution here to fix this issue properly.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Magento\InventoryShipping\Observer\VirtualSourceDeductionProcessor::execute and trace its call to GetActiveStorePickupOrdersBySource::execute, especially order loading through Authorization::afterLoad. Reproduce the free virtual-product checkout with in-store pickup enabled and confirm the existing pickup order can be processed without the customer-authorization failure. Done means the order is placed successfully without disabling the observer.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.