There are plenty of tutorials out there for programatically creating an order in Magento 2.0 however, since upgrading i’ve found that they all no longer work. The error I have been getting is :
Exception #0 (Magento\Framework\Exception\NoSuchEntityException): Cart X does not contain item X
This can be located in :
module-quote/Model/Quote/Item/CartItemPersister.php
1 2 3 4 5 |
if (!$currentItem) { throw new NoSuchEntityException( __('Cart %1 does not contain item %2', $cartId, $itemId) ); } |
It seems that now rather than using :
\Magento\Quote\Model\QuoteFactory
and
\Magento\Quote\Model\QuoteManagement
To save our quote we now need to be using the cart manager
So let me show you the original code I was working with. I followed a tutorial that can be found here by the lovely people over at Webkul
For the sake of this example we can use the object manager to load this class and call orderCreate with some order data like so :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$orderData =[ 'currency_id' => 'USD', 'email' => 'test@example.com', 'shipping_address' =>[ 'firstname' => 'Fake', //address Details 'lastname' => 'Name', 'street' => 'xxxxx', 'city' => 'xxxxx', 'country_id' => 'IN', 'region' => 'xxx', 'postcode' => '43244', 'telephone' => '52332', 'fax' => '32423', 'save_in_address_book' => 1 ], 'items'=> [ ['product_id'=>'1','qty'=>1] ] ]; $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $objectManager->get('Vendor\Namespace\Model\Order\Create')->createOrder($orderData); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
<?php namespace Vendor\Namespace\Model\Order; class Create { public function __construct( \Magento\Framework\App\Helper\Context $context, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Product $product, \Magento\Quote\Model\QuoteFactory $quote, \Magento\Quote\Model\QuoteManagement $quoteManagement, \Magento\Customer\Model\CustomerFactory $customerFactory, \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, // Add this \Magento\Sales\Model\Service\OrderService $orderService // Add this ) { $this->_storeManager = $storeManager; $this->_product = $product; $this->quote = $quote; $this->quoteManagement = $quoteManagement; $this->customerFactory = $customerFactory; $this->customerRepository = $customerRepository; // Add this $this->orderService = $orderService; // Add this } /** * Create Order On Your Store * * @param array $orderData * @return array * */ public function createOrder($orderData) { //init the store id and website id @todo pass from array $store = $this->_storeManager->getStore(); $websiteId = $this->_storeManager->getStore()->getWebsiteId(); //init the customer $customer=$this->customerFactory->create(); $customer->setWebsiteId($websiteId); $customer->loadByEmail($orderData['email']);// load customet by email address //check the customer if(!$customer->getEntityId()){ //If not avilable then create this customer $customer->setWebsiteId($websiteId) ->setStore($store) ->setFirstname($orderData['shipping_address']['firstname']) ->setLastname($orderData['shipping_address']['lastname']) ->setEmail($orderData['email']) ->setPassword($orderData['email']); $customer->save(); } //init the quote $quote=$this->quote->create(); $quote->setStore($store); // if you have already buyer id then you can load customer directly $customer= $this->customerRepository->getById($customer->getEntityId()); $quote->setCurrency(); $quote->assignCustomer($customer); //Assign quote to customer //add items in quote foreach($orderData['items'] as $item){ $product=$this->_product->load($item['product_id']); $product->setPrice(22); $quote->addProduct( $product, intval($item['qty']) ); } //Set Address to quote @todo add section in order data for seperate billing and handle it $quote->getBillingAddress()->addData($orderData['shipping_address']); $quote->getShippingAddress()->addData($orderData['shipping_address']); // Collect Rates and Set Shipping & Payment Method $shippingAddress = $quote->getShippingAddress(); //@todo set in order data $shippingAddress->setCollectShippingRates(true) ->collectShippingRates() ->setShippingMethod('flatrate_flatrate'); //shipping method $quote->setPaymentMethod('checkmo'); //payment method //@todo insert a variable to affect the invetory $quote->setInventoryProcessed(false); $quote->save(); // Set sales order payment $quote->getPayment()->importData(['method' => 'checkmo']); // Collect total and saeve $quote->collectTotals()->save(); // Submit the quote and create the order $order = $this->quoteManagement->submit($quote); //do not send the email $order->setEmailSent(0); //give a result back if($order->getEntityId()){ $result['order_id'] = $order->getEntityId(); }else{ $result=['error'=>1,'msg'=>'Your custom message']; } return $result; } } ?> |
So in order to fix the above we need to do a couple of simple changes.
Firstly we need to update the constructor to add the CartRepositoryInterface and CartManagementInterface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public function __construct( \Magento\Framework\App\Helper\Context $context, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Product $product, \Magento\Quote\Model\QuoteFactory $quote, \Magento\Quote\Model\QuoteManagement $quoteManagement, \Magento\Customer\Model\CustomerFactory $customerFactory, \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, \Magento\Sales\Model\Service\OrderService $orderService, \Magento\Quote\Api\CartRepositoryInterface $cartRepositoryInterface, \Magento\Quote\Api\CartManagementInterface $cartManagementInterface ) { $this->_storeManager = $storeManager; $this->_product = $product; $this->quote = $quote; $this->quoteManagement = $quoteManagement; $this->customerFactory = $customerFactory; $this->customerRepository = $customerRepository; $this->orderService = $orderService; $this->cartRepositoryInterface = $cartRepositoryInterface; $this->cartManagementInterface = $cartManagementInterface; } |
after we set the currency we need to save the quote using the cart repository interface In order to actually create our quote object :
1 2 3 4 |
... $quote->setCurrency(); $this->cartRepositoryInterface->save($quote); // Add this ... |
This then makes $quote->save() lower down redundant as we don’t want to use the regular quote factory to handle our saving. For the same reasons we don’t want the additional save after collect totals :
1 2 3 4 5 6 |
... $quote->setInventoryProcessed(false); //$quote->save(); // comment this line out ... //$quote->collectTotals()->save(); // comment this line out $quote->collectTotals(); // replace it with me as we still need to collect totals |
Then lastly instead of using the quote management interface to actually create our order from the quote we want to use the cart management interface instead :
1 2 3 4 5 6 |
... //$order = $this->quoteManagement->submit($quote); // comment me out // Add this $quote = $this->cartRepositoryInterface->get($quote->getId()); $order = $this->cartManagementInterface->submit($quote); |
And there you go, everything should now be working in Magento2.1
I hope this helps someone out there as I spent a lot of time comparing the original functionality from 2.0 with the admin’s order create process which a rabbit hole of Magento hell!
I’ve put the full class in a gist here
Credit to a lovey person on stack overflow who helped me with the issue. The question can be found here
Thanks for reading !