How to change order states programmatically in Shopware 6
Recently, while browsing the Shopware 6 official forum, I have encountered an interesting thread. One of the fellow developers was struggling with a task to change the default behavior of Shopware 6, so that if a payment state has changed to “in progress”, the state of the whole order should change to “in progress” as well. I have not done this before, so I took it upon myself to try it and learn something from it. I have resolved it successfuly, so let me share the solution with you.
This is a typical use case for a Shopware 6 subscriber. Subscriber is a mechanism, that allows you to run your function, when a certain event happens. You just need to tell Shopware, which method you want to trigger on which event. In our case, we want to subscribe to the ORDER_TRANSACTION_WRITTEN_EVENT. This event is activated every time Shopware writes data to the order_transaction table. That is perfect for our needs, because this table contains information about payments, including payment states. In our function, that will get called immediately after the data is written, we will detect the current payment state. Based on this state, we will set the state of the whole order accordingly. The whole code for changing order states in Shopware 6, based on payment state, can look like this:
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 |
<?php namespace TestPlugin\Subscriber; use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates; use Shopware\Core\Checkout\Order\OrderStates; use Shopware\Core\Checkout\Order\OrderEvents; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class Subscriber implements EventSubscriberInterface { private $orderRepository; private $stateMachineStateRepository; public function __construct( EntityRepositoryInterface $orderRepository, EntityRepositoryInterface $stateMachineStateRepository ) { $this->orderRepository = $orderRepository; $this->stateMachineStateRepository = $stateMachineStateRepository; } public static function getSubscribedEvents(): array { return [ OrderEvents::ORDER_TRANSACTION_WRITTEN_EVENT => 'onOrderTransactionWritten' ]; } public function onOrderTransactionWritten (EntityWrittenEvent $event) { $payloads = $event->getPayloads(); foreach ($payloads as $payload) { //get the state of the transaction $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('id', $payload['stateId'])); $data = $this->stateMachineStateRepository->search($criteria, $event->getContext()); if ($data) { $currentOrderState = $data->first(); //if the transaction state is "in progress", if ($currentOrderState->getTechnicalName() === OrderTransactionStates::STATE_IN_PROGRESS) { //get the ID of the order state, that we want to set $criteria = new Criteria(); $criteria->addAssociation('stateMachine'); $criteria->addFilter(new EqualsFilter('technicalName', OrderStates::STATE_IN_PROGRESS)); $criteria->addFilter(new EqualsFilter('stateMachine.technicalName', OrderStates::STATE_MACHINE)); $data = $this->stateMachineStateRepository->searchIds($criteria, $event->getContext()); $newOrderStateId = $data->firstId(); //change the state of the order $update = [ 'id' => $payload['orderId'], 'stateId' => $newOrderStateId ]; $this->orderRepository->update([$update], $event->getContext()); } } } } } |
This is a class named simply Subscriber, that should be located in a Subscriber subdirectory of your plugin. In order to make things work, you also need to register your subscriber, so that Shopware knows about it. This is done inside an XML file, named services.xml, that should be located in your plugin as well in src/Resources/config subdirectory. It should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="TestPlugin\Subscriber\Subscriber"> <argument type="service" id="order.repository"/> <argument type="service" id="state_machine_state.repository"/> <tag name="kernel.event_subscriber"/> </service> </services> </container> |
This is simply the wrong approach, by writing the new State directly to the DB you surpass a whole lot of shopware functionality, no email will be sent, no products will be updated etc. the right approach would be to use the OrderService class
Hi, thanks for the comment. I don’t think it is necessarily a wrong approach for that particular situation, that I was looking into. He wanted to change the state of the order, based on the state of the payment, which means, it would not follow the standard Shopware 6 workflow anyway. I am not sure, that sending emails to the customers would be desirable in that case, so I have just offered a way, that does, what he wanted, without any collateral damage. 😉 But you are right, the OrderService offers a way to achieve this as well. It might actually be a good topic for a future article. 🙂
I think it doesn’t work properly in the new version of Shopware 6. Here is the right way to do this –
https://developer.shopware.com/docs/guides/plugins/plugins/checkout/order/using-the-state-machine
I have tested the repository approach in Shopware version 6.3.5.1 and it worked there. In any case, the “state machine” approach is definitely a way to go in standard cases, so thanks for the useful link.