Question

Has anyone figured out how to programatically create an order in Magento 2.1 yet ?

There are a few tutorials kicking around for 2.0 which work nicely but every since I upgraded it no longer works. The error I most often see is:

Exception #0 (Magento\Framework\Exception\NoSuchEntityException): Cart 88 does not contain item 91

Obviously the cart ID and item ID change with every attempt.

I have been looking at admin order create and the only difference is when we hit the file:

vendor/magento/module-quote/Model/QuoteRepository/SaveHandler.php

In the save() function when processing an order in the admin $items = $quote->getItems(); is null whereas when programmatically creating an order we have an item still.

Anyone know why this is happening? Or just know how to create an order?

My class can be found here:
https://gist.github.com/TheFrankman/21fdf70883d4e2d717333ac67f1e0f2d

But there is quite a lot going on. Most logic happens in function createOrder().

If you use the code that's in this question : How to create order programmatically in Magento 2?

You will get the same error. It might be easier to follow than my example


EDIT:

Big thanks to Jaimin Parikh for helping me out with this.

I thought it might be useful to put together a quick blog post about the differences between 2.0 and 2.1 now that I've solved my problem with a more simplified example. Here is the link:

http://frankclark.xyz/magento2-1-programatically-create-order

==============================

Another side note to this if anyone still has problems after the above. You need to us the environment emulation. Various checks in magento core get the current store view, which, if you are running this from a cron for example, will return as store id 0 which may not match the quote that you are creating.

\Magento\Store\Model\App\Emulation $emulation

Was it helpful?

Solution

I faced same error while placing order programatically.

I have made some changes in your createOrder() function.

public function createOrder(Subscriptions $subscription, $originalOrder, $originalOrderItemId) {
        /**
         * @var $originalOrder \Magento\Sales\Model\Order
         * @var $quote         \Magento\Quote\Model\Quote
         * @var $customer
         */
        // Firstly make sure we can load the customer
        $customerId = $originalOrder->getCustomerId();
        //$customer = $this->_customerFactory->create()->load($customerId);
        $customer = $this->_customerInterface->getById($customerId);
        if ($customer->getEmail()) {
            // Get some data from the original order that we are going to need
            // @todo : load isn't deprecated it might be later
            $originalStoreId = $originalOrder->getStoreId();
            $store = $this->_store->load($originalStoreId);
            // Create a blank quote
            $quote = $this->_quoteFactory->create()->setStoreId($originalStoreId);

            /**
             * Added by Jaimin
             */
            $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
            $quoteRepository = $objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class);
            $quoteRepository->save($quote);
            /**
             * End
             */
            // Assign the customer to the quote
            $quote->assignCustomer($customer);
            //@todo : fix error with below - must implement interface Magento\Quote\Api\Data\CurrencyInterface
            //$currency = $originalOrder->getBaseCurrencyCode();
            //$quote->setCurrency($currency);
            //temporary - currency needs to be the purchased currency
            $quote->setCurrency();
            // Build Billing Address
            $billingAddress = clone $originalOrder->getBillingAddress();
            $billingAddress->unsetData('entity_id')->unsetData('parent_id')
                    ->unsetData('customer_address_id')->unsetData('customer_id')
                    ->unsetData('quote_address_id');
            // Build Shipping Address
            // @todo : Customers are going to need the ability to change their shipping address come back and fix this once that's done
            $shippingAddress = clone $originalOrder->getShippingAddress();
            $shippingAddress->unsetData('entity_id');
            // Insert the address details
            $quote->getBillingAddress()->addData($billingAddress->getData());
            $quote->getShippingAddress()->addData($shippingAddress->getData());
            /**
             * @var $subscriptionOrderItem \Magento\Sales\Model\Order\Item
             */
            // Load the subscription product from original order
            $subscriptionOrderItem = $originalOrder->getItemById(
                    $originalOrderItemId
            );
            // Make sure we have a product
            if ($subscriptionOrderItem instanceof SalesOrderItem) {
                // Get params required to add product
                $params = $this->getProductParams($subscriptionOrderItem);
                // Load the product
                $product = $this->getSubscriptionProduct(
                        $subscriptionOrderItem, $originalStoreId
                );
                // Product may have been deleted
                if (!$product) {
                    $message = sprintf(
                            __('Could not load product id %s for subscription %s'), $subscription->getProductId(), $subscription->getId()
                    );
                    $this->_logger->critical($message);
                    throw new LocalizedException($message);
                }
                //$quote->addProduct($product, $params);
                $this->_quoteInitializer->init($quote, $product, $params);
                // Set the product price based on original order incase product price changed
                try {
                    $this->setProductPrices(
                            $quote, $product, $subscription->getProductCost()
                    );
                } catch (LocalizedException $e) {
                    $message = __(
                            'Unable set custom price - cannot release subscription'
                    );
                    $this->_logger->critical($message);
                    throw new LocalizedException($message, $e);
                }
                $quote->setTotalsCollectedFlag(false)->collectTotals();
            } else {
                $message = __(
                                'Could not load original order item for subscription : '
                        ) . $subscription->getId();
                $this->_logger->critical($message);
                throw new LocalizedException(__($message));
            }
            // Collect shipping rates
            $quote->getShippingAddress()->setCollectShippingRates(true)
                    ->collectShippingRates();
            // Apply the correct shipping rate to this order
            $this->getShippingRate($quote, $originalOrder);
            // Set payment method - work still to be done here
            $quote->setPaymentMethod($this->_getPaymentMethod());
            // Create the quote

            /**
             * Change by Jaimin
             */
            //$quote->save();

            /**
             * End
             */
            // Import payment data - Don't understand this yet
            $quote->getPayment()->importData(
                    array('method' => $this->_getPaymentMethod())
            );
            // Collect quote totals
            $quote->collectTotals()->save();
            /**
             * @var $order \Magento\Sales\Model\Order
             */
            // Convert the quote to an order
            //$order = $this->_quoteManagement->submit($quote);

            /**
             * Added by Jaimin
             */
            $quoteManagement = $objectManager->create(\Magento\Quote\Api\CartManagementInterface::class);
            $quote = $quoteRepository->get($quote->getId());
            $order = $quoteManagement->submit($quote);
            /**
             * End
             */
            // Prevent default order confirmation, we will make our own
            $order->setEmailSent(0);
            // Make sure the order has been created properly
            if (!$order->getRealOrderId()) {
                $order = false;
            }
            $this->_logger->info(
                    sprintf(
                            __('Order %s succesfully created for subscription %s'), $order->getRealOrderId(), $subscription->getId()
                    )
            );
            return $order;
        } else {
            $message = __('The customer no longer exists for subscription: ')
                    . $subscription->getId();
            $this->_logger->critical($message);
            throw new LocalizedException(__($message));
        }
    }

OTHER TIPS

...

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;
    }

    /**
     * @return int
     */
    public function test()
    {
        return 1;
    }

    /**
     * @return array
     */
    public function createOrder()
    {
        $store = $this->_storeManager->getStore();
        $websiteId = $this->_storeManager->getStore()->getWebsiteId();

        $customer = $this->customerFactory->create();
        $customer->setWebsiteId($websiteId);
        $customer->loadByEmail('bradf@home.net');

        if(!$customer->getEntityId()){
            $customer->setWebsiteId($websiteId)
                ->setStore($store)
                ->setFirstname('Customer')
                ->setLastname('Name')
                ->setEmail('bradf@home.net')
                ->setPassword('1234567');
            $customer->save();
        }

        $quote = $this->quote->create();
        $quote->setStore($store);

        $customer = $this->customerRepository->getById($customer->getEntityId());
        $quote->setCurrency();

        $quote->save();

        $quote->assignCustomer($customer);

        $product = $this->_product->load(1);
        $product->setPrice(12);
        $quote->addProduct($product,1);

        $address = [
            'firstname' => 'First',
            'lastname' => 'Last',
            'street' => '12345 1234 st',
            'city' => 'cityville',
            'country_id' => 'US',
            'region' => 'WA',
            'postcode' => '98765',
            'telephone' => '4254254254',
            'fax' => '4254254255',
            'save_in_address_book' => 1
        ];

        $quote->getBillingAddress()->addData($address);
        $quote->getShippingAddress()->addData($address);

        $shippingAddress = $quote->getShippingAddress();

        $shippingAddress->setCollectShippingRates(true)
            ->collectShippingRates()
            ->setShippingMethod('flatrate_flatrate');
        $quote->setPaymentMethod('checkmo');

        $quote->setInventoryProcessed(false);

        $quote->getPayment()->importData(['method'=>'checkmo']);

        $quote->collectTotals()->save();

        $order_id = $this->quoteManagement->placeOrder($quote->getId());

        if($order_id){
            $retval['order_id'] = $order_id;
        } else {
            $retval['error'] = 'order not created';
        }
        return $retval;
    }

...

This solution is working till Magento2.3.5. The following error occurred with Magento2.3.6 and Magento2.4 versions. Any help would be appreciated.

Type Error occurred when creating object: Magento\InventoryIndexer\Model\Queue\ReservationData, Argument 2 passed to Magento\InventoryIndexer\Model\Queue\ReservationData::__construct() must be of the type int, null given, called in /var/www/html/magento2/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php on line 121

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top