E-commerce illustration
Profile picture for user eirik
Written by
Eirik S. Morland
Published on
September 23, 2020

How to trigger an order state transition event subscriber programmatically

Here at Ny Media we are specialists in developing highly customized Drupal Commerce solutions. One of the recurring themes of a tailor-made e-commerce solution, is to integrate some sort of order export towards the ERP solution that the client in question uses. And a very common way for us to do that is to hook into commerce order state transitions.

Typically a subscriber would make sure the actual export is queued through Drupal's queue system (or through Advanced Queue). This makes it possible to have exports that can retry, delay execution and generally be reliable and fault-tolerant.

However, this also involves integration towards third parties. No matter how fault-tolerant your integration is, you can not guard against all kinds of error in a third party application. Which is why from time to time I find myself in need of a way to trigger these transitions again, for example to re-queue the order export. So here is an annotated code example with how one can achieve this:


// Replace this with the order id in question.
$order_id = 123456;
// If you want to do this within a class, probably use dependency injection
// instead of the Order class directly. The same goes for the services we are
// about to load statically from the service container.
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = Order::load($order_id);
// This service is the event subscriber. We want to trigger the event
// subscriber only, and not dispatch the event from the event emitter so all
// subscribers can react to it.
/** @var \Drupal\nymedia_order_export\EventSubscriber\NymediaOrderExportSubscriber $order_export_event_subscriber */
$order_export_event_subscriber = \Drupal::service('nymedia_order_export.event_subscriber');
/** @var \Drupal\state_machine\WorkflowManager $workflow_plugin_manager */
$workflow_plugin_manager = \Drupal::service('plugin.manager.workflow');
// The type of order workflow you are using might vary from your site. See
// below for a note on how to find out.
/** @var \Drupal\state_machine\Plugin\Workflow\WorkflowInterface $workflow */
$workflow = $workflow_plugin_manager->createInstance('order_fulfillment');
// The transition in question will also vary based on your site. For a note on
// how to find out, read below.
$transition = $workflow->findTransition('draft', 'fulfillment');
$event = new WorkflowTransitionEvent($transition, $workflow, $order, 'state');
// Now trigger the event. The method here will be the one you have indicated
// will respond to the event. For example, if your event subscriber service
// has this in its public static function getSubscribedEvents:
// 'commerce_order.place.post_transition' => ['onPlace'],
// (the example is taken from this class itself), then you probably want to
// invoke the method ::onPlace, like we are doing here. If you are looking for
// another transition, or your class has a different method as a responder for
// the place transition event, then you probably want to adjust this
// accordingly.

As stated in the code above, there are some things that might not be obvious how you find out. Let's start with the order workflow ID.

How to find the workflow ID of an order type

To find out what kind of workflow you are using, you take a look at the order type for the order in question. On this particular site, there are 2 order types. The one that I had to re-queue is of the type "default". To edit the order type "default", go to mysite.com/admin/commerce/config/order-types/default/edit (change mysite.com to the address of your Drupal site).

In there we can see that you can select the workflow for the order. And in the select dropdown, I can see that the ID for the chosen one is "order_fulfillment":

<option value="order_fulfillment" selected="selected">Fulfillment</option>

Another way to find this ID is to look at the configuration YML file for the default order type. It will look something like this, and in my case is called "commerce_order.commerce_order_type.default.yml":

uuid: 2a999511-7dbb-4a44-9599-6bad702ff032
# ... Content edited out for clarity.
label: Standard
id: default
# Here is the id of the workflow again:
workflow: order_fulfillment
# ... rest of the file.

How to find state IDs for the workflow transition

In my example, I wanted to get the transition where an order goes from "draft" to "placed". How did I know these IDs, and how did I know which ones to use?

To answer that, we should check out the definition of the workflow we have chosen. A workflow will most often be defined in a file called modulename.workflows.yml. In our case, we are actually using one of the workflows provided out of the box with Drupal Commerce, so the file to check out is commerce_order.workflows.yml. In here our workflow is defined as follows:

  id: order_fulfillment
  group: commerce_order
  label: 'Fulfillment'
      label: Draft
      label: Fulfillment
      label: Completed
      label: Canceled
      label: 'Place order'
      from: [draft]
      to:   fulfillment
      label: 'Fulfill order'
      from: [fulfillment]
      to: completed
      label: 'Cancel order'
      from: [draft, fulfillment]
      to:   canceled

We can see it has 4 states, and 3 transitions. The one we want is the one that is triggered when the user completes their purchase, and this is the transition with the ID "place". To find it, I used the state it was supposed to transition from and the state is was supposed to transition to, which you can see is "draft" and "fulfillment".

As you can see, the framework of Drupal Commerce makes it possible for us to create advanced types of integrations, which are fault-tolerant, flexible and scalable. If you are looking for a partner to develop a Drupal Commerce site with customized integrations, contact our team of experts, ready to create solutions for you, regardless of integration types, order types or workflow types.