How to trigger an order state transition event subscriber programmatically
E-commerce illustration
eirikSeptember 23, 2020

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 needing 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:

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.
$order_export_event_subscriber->onPlace($event);

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". So I edit that order type, which can be found at mysite.com/admin/commerce/config/order-types/default/edit.

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":

Another way to see this 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 look up 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 look at is commerce_order.workflows.yml. In here our workflow is defined as follows:

order_fulfillment:
  id: order_fulfillment
  group: commerce_order
  label: 'Fulfillment'
  states:
    draft:
      label: Draft
    fulfillment:
      label: Fulfillment
    completed:
      label: Completed
    canceled:
      label: Canceled
  transitions:
    place:
      label: 'Place order'
      from: [draft]
      to:   fulfillment
    fulfill:
      label: 'Fulfill order'
      from: [fulfillment]
      to: completed
    cancel:
      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.