5

To finish implementing a custom payment method I need to know how to convert the quote to the actual order.

From payment gateway, my custom controller is called.

Inside that controller, I can access Checkout Session object.

When I call getQuoteId on that object, I get the actual quote id, however, when I call getLastRealOrderId, nothing is returned, so I think I need to do a previous step first.

I have read https://mage2.pro/t/topic/732 and https://mage2.pro/t/topic/731 but those sites only shows code, with no explanation, so I need a little guide to use method calls shown there.

The order should be created in both when failure and success payment occured.

Furthermore, when success payment, the confirmation e-mail for customer should be sent automatically.

How can I do it?

EDIT:

This is a controller that is called using $.post jquery method:

class Store extends \Magento\Framework\App\Action\Action {

/**
 * @var \Psr\Log\LoggerInterface
 */
protected $_logger;

/**
 * @param \Magento\Framework\App\Action\Context $context
 * @param \Psr\Log\LoggerInterface $logger
 */
public function __construct(
        \Magento\Framework\App\Action\Context $context, 
        \Psr\Log\LoggerInterface $logger
) {
    $this->_logger = $logger;
    parent::__construct($context);
}

/**
 * Almacena los datos para posterior recuperacion
 *
 * @return void
 * @SuppressWarnings(PHPMD.ExitExpression)
 */
public function execute() {
    if (!$this->getRequest()->isPost()) {
        return;
    }

    try {
        $data = $this->getRequest()->getPostValue();
        ----
    }
    } catch (RemoteServiceUnavailableException $e) {
        $this->_logger->critical($e);
        $this->getResponse()->setStatusHeader(503, '1.1', 'Service Unavailable')->sendResponse();
        /** @todo eliminate usage of exit statement */
        exit;
    } catch (\Exception $e) {
        $this->_logger->critical($e);
        $this->getResponse()->setHttpResponseCode(500);
    }
}

Quote Id is received by POST method in execute() function. I need to be able to create the order from there. How can I define the constructor in the proper way? I am thinking that maybe constructor parameters are specified as @params comments, aren't they? I would found this to be strange, since those are comments in PHP, but maybe Magento uses that information to do such a definition.

4 Answers 4

4

I'm going to try to make it simple and understandable.

So to convert a quote to an order, you need to use the \Magento\Quote\Api\CartManagementInterface interface.

Use dependency injection in your constructor to have an accessible variable for this one:

protected $_quoteManagement;

public function __construct(\Magento\Framework\App\Action\Context $context,
    \Psr\Log\LoggerInterface $logger,
    \Magento\Quote\Api\CartManagementInterface $quoteManagement)
{
    $this->_logger = $logger;
    //$this->_quoteManagement = $quoteManagement;
    $this->quoteManagement = $quoteManagement;
    parent::__construct($context);
}

Then to convert a quote to an order you can do the following in your execute method:

$order = $this->quoteManagement->submit($quote);

Regarding the getLastRealOrderId it is not managed automatically by the order creation and thus you need to use your _checkoutSession object like this:

$this->_checkoutSession
    ->setLastOrderId($order->getId())
    ->setLastRealOrderId($order->getIncrementId());

If you need more details about quote conversion I reckon you should have a look at the saveOrder() method of \Magento\Checkout\Model\Type\Onepage.php

14
  • This is clear but dependency injection. If I just add a new parameter to constructor, application throws an exception telling that the number or types of parameters are incorrect. I have read several documents about dependency injection but they are not clear enough to understand the thing in practice. How can I define constructor parameters?
    – jstuardo
    Apr 11, 2016 at 11:10
  • @jstuardo yeah you have to follow the parent constructor order but somehow sometimes it is not clear as you said. Feel free to post your class and I'll help you setting it up. Apr 11, 2016 at 11:11
  • @jstuardo I have updated my answer accordingly to your question update Apr 11, 2016 at 14:56
  • It was the same problem I have got before: Recoverable Error: Argument 3 passed to Desytec\Transbank\Controller\Purchase\Store::__construct() must be an instance of Magento\Quote\Api\CartManagementInterface, none given
    – jstuardo
    Apr 11, 2016 at 21:00
  • I call the controller only by calling its URL. Do I need to define something in the XML file?
    – jstuardo
    Apr 11, 2016 at 21:01
0

I just ran into the same problem with payments. I traced that back to \Magento\Quote\Model\Quote\Payment\ToOrderPayment::convert():

public function convert(Payment $object, $data = [])
{
    $paymentData = $this->objectCopyService->getDataFromFieldset(
        'quote_convert_payment',
        'to_order_payment',
        $object
    );

    $orderPayment = $this->orderPaymentFactory->create();
    $this->dataObjectHelper->populateWithArray(
        $orderPayment,
        array_merge($paymentData, $data),
        '\Magento\Sales\Api\Data\OrderPaymentInterface'
    );
    $orderPayment->setAdditionalInformation(
        array_merge(
            $object->getAdditionalInformation(),
            [Substitution::INFO_KEY_TITLE => $object->getMethodInstance()->getTitle()]
        )
    );
    // set directly on the model
    $orderPayment->setCcNumber($object->getCcNumber());
    $orderPayment->setCcCid($object->getCcCid());

    return $orderPayment;
}

It starts by processing the fieldset to copy data from $object into the $paymentData array, but then feeds that array into \Magento\Framework\Api\DataObjectHelper::populateWithArray() to actually set the values on the target object (here, $orderPayment).

Rather than directly adding the data array to the object as in Magento 1, DataObjectHelper looks for 'set__' or 'setIs__' methods corresponding to each key, and then calls those methods with the value. If such method does not exist, the data key is skipped entirely.

Quote addresses and items are processed the same way, in \Magento\Quote\Model\Quote\Address\ToOrderAddress and \Magento\Quote\Model\Quote\Item\ToOrderItem respectively.

What does that mean?

Adding the key to a fieldset is not sufficient, unless a set method already exists on the object corresponding to that key. It appears that copying custom fields from quote to order will require either overriding the target class to add the necessary setter method(s) (via dependency injection), or using observers like sales_model_service_quote_submit_before to copy the data manually.

Also note that the quote module has its own fieldsets defined, including quote_convert_address and quote_convert_item. It does not appear that the sales_convert_quote* fieldsets are in use anymore.

OR

For Magento 2 - this will help you

0

If you already have a quote

Simply do following code to convert quote to order

$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$paymentMethod = "your payment method code here";
$quote->setPaymentMethod($paymentMethod);
$quote->getPayment()->importData(['method' => $paymentMethod]);
$quote->collectTotals();
$quote->save();                
$objectManager->create('\Magento\Quote\Api\CartManagementInterface)
              ->placeOrder($quote->getId());

In my case I already had a shipping info and customer/guest info in quote

-3

I donno how you have implemented your module, but if u already have quoteId, then you can try this once..

PS - Please test this properly before using in live server

    $quoteObj = Mage::getModel('sales/quote')->load($quoteId);
    $items = $quoteObj->getAllItems();
    $quoteObj->collectTotals();
    $quoteObj->save();
    $quoteObj->reserveOrderId();
    $quotePaymentObj = $quoteObj->getPayment();

    $convertQuoteObj = Mage::getSingleton('sales/convert_quote');

    $orderObj = $convertQuoteObj->addressToOrder($quoteObj->getShippingAddress());
    $orderPaymentObj = $convertQuoteObj->paymentToOrderPayment($quotePaymentObj);

    $orderObj->setBillingAddress($convertQuoteObj->addressToOrderAddress($quoteObj->getBillingAddress()));
    $orderObj->setShippingAddress($convertQuoteObj->addressToOrderAddress($quoteObj->getShippingAddress()))
            ->setShipping_method($shipping_method)
            ->setShippingDescription($shipping_method_description);
    $orderObj->setPayment($convertQuoteObj->paymentToOrderPayment($quoteObj->getPayment()));


    foreach ($items as $item) {
        //@var $item Mage_Sales_Model_Quote_Item
        $orderItem = $convertQuoteObj->itemToOrderItem($item);
        if ($item->getParentItem()) {
            $orderItem->setParentItem($orderObj->getItemByQuoteItemId($item->getParentItem()->getId()));
        }
        $orderObj->addItem($orderItem);
    }
    $orderObj->setCanShipPartiallyItem(false);
    $totalDue = $orderObj->getTotalDue();

try{
    $orderObj->place(); //calls _placePayment
    $orderObj->save();
}
catch (Exception $ex) {
    echo $ex->getMessage();

}
1
  • The question is Magento 2 and you are given answer magento1
    – Abdul
    Apr 6, 2016 at 6:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.