Link Magento 2 Guest Orders to Customer Accounts

In newer Magento versions, there is the ability for customers to create an account after they have placed an order as a guest. This allows the customer the ability to track their order. There might be times where customers have placed orders in the past as guests and have only recently registered an account with you. In order to link Magento 2 guest orders to customer accounts, some code needs to be written.

This is a nicer approach than creating customer accounts without their consent, and assigning orders that way.

As a time can’t really be placed on when customers register on your website, the functionality to link orders can be executed via a cron schedule.

To start with, create a custom module with a registration.php and module.xml file.

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    '[Vendor]_[Module]',
    __DIR__
);
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="[Vendor]_[Module]" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Customer"/>
            <module name="Magento_Sales"/>
        </sequence>
    </module>
</config> 

Now configure the crontab configuration. This can be done by creating the module’s crontab.xml file.

You’ll also be able to define the frequency of which the code to link the orders is run. As an example, the code below configures the code to run every day at 1AM.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="link_guest_orders_to_account" instance="[Vendor]\[Module]\Cron\LinkOrders" method="execute">
            <schedule>0 1 * * *</schedule>
        </job>
    </group>
</config>

The above configuration defines a LinkOrders class within the module’s Cron directory.

<?php
namespace [Vendor]\[Module]\Cron;

use [Vendor]\[Module]\Model\LinkOrders as LinkOrdersModel;

class LinkOrders
{
    /**
     * @var LinkOrdersModel
     */
    private $linkOrdersModel;

    /**
     * LinkOrders constructor.
     *
     * @param LinkOrdersModel $linkOrders
     */
    public function __construct(
        LinkOrdersModel $linkOrders
    ) {
        $this->linkOrdersModel    = $linkOrders;
    }

    public function execute()
    {
        $this->linkOrdersModel->assignOrders();
    }
}

Within the execute() method, we simply delegate to the assignOrders method of the [Vendor]\[Module]\Model\LinkOrders class.

The model class is where the majority of the functionality to link Magento 2 guest orders to customer accounts.

Firstly, a collection of the recently registered customers in the last 24 hours is retrieved within the getNewCustomers() method. This method gets called within the main assignOrders() method.

Next, a foreach loop around the customers is created. Within this loop, the customer’s email address using $customer->getEmail() is used as an attribute filter when retrieving the order collection. This, along with the customer_is_guest attribute returns a list of guest orders made by the customer using the same email address.

Finally, a foreach loop is created for the orders returned. Within this loop, some of the customer-related data is modified to assign the order to the registered customer account.

<?php
namespace [Vendor]\[Module]\Model;

use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Api\Search\SearchCriteriaBuilder;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Message\ManagerInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory;

class LinkOrders
{
    /**
     * @var CustomerRepositoryInterface
     */
    private $customerRepository;

    /**
     * @var FilterBuilder
     */
    private $filterBuilder;

    /**
     * @var SearchCriteriaBuilder
     */
    private $searchCriteria;

    /**
     * @var ManagerInterface
     */
    private $messageManager;

    /**
     * @var OrderRepositoryInterface
     */
    private $orderRepository;

    /**
     * @var OrderCollectionFactory
     */
    private $orderCollectionFactory;

    /**
     * LinkOrders constructor.
     *
     * @param CustomerRepositoryInterface $customerRepository
     * @param FilterBuilder $filterBuilder
     * @param SearchCriteriaBuilder $searchCriteria
     * @param ManagerInterface $messageManager
     * @param OrderRepositoryInterface $orderRepository
     * @param OrderCollectionFactory $orderCollectionFactory
     */
    public function __construct(
        CustomerRepositoryInterface $customerRepository,
        FilterBuilder $filterBuilder,
        SearchCriteriaBuilder $searchCriteria,
        ManagerInterface $messageManager,
        OrderRepositoryInterface $orderRepository,
        OrderCollectionFactory $orderCollectionFactory
    ) {
        $this->customerRepository = $customerRepository;
        $this->filterBuilder = $filterBuilder;
        $this->searchCriteria = $searchCriteria;
        $this->messageManager = $messageManager;
        $this->orderRepository = $orderRepository;
        $this->orderCollectionFactory = $orderCollectionFactory;
    }

    /**
     * Get newly registered customers
     *
     * @return \Magento\Customer\Api\Data\CustomerInterface[]
     */
    public function getNewCustomers()
    {
        // Get registered customers within the last 24h
        $date = new \DateTime();
        $startDate = $date->modify('-1 day');
        $createdAtFilter = $this->filterBuilder->setField('created_at')
            ->setConditionType('gteq')
            ->setValue($startDate)
            ->create();

        $searchCriteria = $this->searchCriteria
            ->addFilter($createdAtFilter)
            ->create();

        try {
            $collection = $this->customerRepository->getList($searchCriteria);
            return $collection->getItems();
        } catch (LocalizedException $e) {
            $this->messageManager->addExceptionMessage(
                $e,
                $e->getMessage()
            );
        }
    }

    /**
     * Assign orders to newly registered customers
     */
    public function assignOrders()
    {
        $newCustomers = $this->getNewCustomers();
        foreach ($newCustomers as $customer) {
            $orderCollection = $this->orderCollectionFactory->create()
                ->addAttributeToFilter('customer_email', $customer->getEmail())
                ->addAttributeToFilter('customer_is_guest', 1);

            foreach ($orderCollection as $order) {
                $order->setCustomerId($customer->getId());
                $order->setCustomerFirstname($customer->getFirstname());
                $order->setCustomerLastname($customer->getLastname());
                $order->setCustomerGroupId($customer->getGroupId());
                $order->setCustomerIsGuest(0);
                $this->orderRepository->save($order);
            }
        }
    }
}

That’s the basics of creating a module to link Magento 2 guest orders to customer accounts. You may wish to modify the cron schedule and other parts of the code to suit the needs of your store.

Note: This article is based on Magento Open Source version 2.2.