 [ ← blog ](/) Oct 10, 2017  ·  5 min read 

# Implementing a Checkout Flow resolver in Drupal Commerce 2.0



**This is a new format I'm trying which is more tutorial based. Let me know what you think! I plan on more regular content like this.**

## What are we going to learn?

Drupal Commerce 2 allows you to support multiple checkout forms. This is pretty cool because it allows you to have different checkout experiences for your customers based on what they are purchasing. We're going to dive into why you would want to use a checkout flow resolver and how to implement one.

- Understand the order and checkout relationship
- Implement a checkout flow resolver service

![CheckoutResolverInterface.php](/sites/default/files/2017-09/checkout-resolver-interface.png)

## Getting started

We are going to create a checkout flow resolver which inspects the customer's order. The goal of our checkout flow resolver is to see if the order contains both physical and digital goods. If the order contains both types of products, we're going to provide a checkout flow plugin that should be used. This will then be the checkout flow plugin used, ensuring our customer has the correct checkout experience.

By default, a checkout flow uses the one configured on the order type. By following this tutorial you could rely on a single order type instead of multiple order types (one for physical, one for digital.)

To keep the tutorial short, we are going to assume there are the following checkout flows configured:

- Digital (machine name: *digital*)
- Shipping (machine name: *shipping*)
- Mixed order (machine name: *mixed\_order*)

You should also have a custom module, which we will refer to as *mymodule*.

## Determining an order's checkout flow

The checkout module adds a setting to order types. This setting specifies what checkout flow the order will use. This setting is the default value used and can be overridden through checkout flow resolvers. The [concept of resolvers](https://docs.drupalcommerce.org/commerce2/developer-guide/core/understanding-resolvers) is used quite often in Drupal Commerce. It is a design pattern which uses a "first come first serve" basis. Once a resolver returns a value, that value is used.

In the case for checkout flows, this is performed by the **commerce\_checkout.chain\_checkout\_flow\_resolver** service. It looks for services tagged as **commerce\_checkout.checkout\_flow\_resolver**. If you aren't familiar with tagged services, that is fine. There's an example below. Or you can learn more about them on the Drupal.org [Service Tags](https://www.drupal.org/docs/8/api/services-and-dependency-injection/service-tags) documentation.

The Commerce Checkout module provides a checkout flow resolver, **commerce\_checkout.default\_checkout\_flow\_resolver**. This is the default resolver. It's intended to run last and will provide the default value for a checkout flow. The one on the order type's setting.

An order's checkout flow is discovered through the **commerce\_checkout.checkout\_order\_manager** service. When an order enters the checkout process that service is invoked. It then looks up the order's checkout flow through the chain resolver.

## Creating the resolver

Our first step is to create the resolver. In your module's directory, create an **src/Resolvers** directory. Nothing requires resolvers go to into a **Resolver** namespace, but it's just more organized. Then create a **CheckoutFlowResolver.php** in that directory.

Inside of our **CheckoutFlowResolver.php** file let's sub out our class.

```php
<?php

namespace Drupal\mymodule\Resolvers;

use Drupal\commerce_checkout\Resolver\CheckoutFlowResolverInterface;

class CheckoutFlowResolver implements CheckoutFlowResolverInterface {

}
```

We need to satisfy the requirements of the **CheckoutFlowResolverInterface** interface. To do so, we implement a the **resolve** method which accepts an order object.

```php
<?php

namespace Drupal\mymodule\Resolvers;

use Drupal\commerce_checkout\Resolver\CheckoutFlowResolverInterface;
use Drupal\commerce_order\Entity\OrderInterface;

class CheckoutFlowResolver implements CheckoutFlowResolverInterface {

  /**
   * {@inheritdoc}
   */
  public function resolve(OrderInterface $order) {
    // TODO: Implement resolve() method.
  }

}
```

Inside of our **resolve** method, we can implement any logic necessary to determine the checkout flow that should be used. If we do not choose to return a checkout flow, the resolving logic will move onto the next resolver. By default, this will then fallback to the checkout flow configured on the order type itself.

Let's assume our site has a **digital** order type which uses the **digital** checkout flow. There is also the **default** order type which uses the **shipping** checkout flow. In our example, we want to provide a way for mixed orders to use a different checkout flow called **mixed\_order.**

We'll update our checkout flow resolver to check the order's bundle. If a digital order has shippable products or a shippable order has a digital product we will return a checkout flow and override the default.

```php
<?php

namespace Drupal\mymodule\Resolvers;

use Drupal\commerce_checkout\Entity\CheckoutFlow;
use Drupal\commerce_checkout\Resolver\CheckoutFlowResolverInterface;
use Drupal\commerce_order\Entity\OrderInterface;

class CheckoutFlowResolver implements CheckoutFlowResolverInterface {

  /**
   * {@inheritdoc}
   */
  public function resolve(OrderInterface $order) {
    if ($order->bundle() == 'digital') {
      foreach ($order->getItems() as $item) {
        // Shippable items have a weight field.
        if ($item->hasField('weight')) {
          return CheckoutFlow::load('mixed_order');
        }
      }
    }
    else {
      // Check if the default order has digital items.
      foreach ($order->getItems() as $item) {
        if (!$item->hasField('weight')) {
          return CheckoutFlow::load('mixed_order');
        }
      }
    }
  }

}
```

## Registering our resolver

We have created the resolver. Drupal is not aware of it, yet. We must create a **mymodule.services.yml** which registers the class to Drupal's service container. We will then *tag* it as a checkout flow resolver.

```yaml
services:
  mymodule.checkout_flow_resolver:
    class: Drupal\mymodule\Resolvers\CheckoutFlowResolver
    tags:
      - { name: commerce_checkout.checkout_flow_resolver, priority: 100 }
```

The **commerce\_checkout.services.yml** file defines the service which collects all services using the **commerce\_checkout.checkout\_flow\_resolver** tags. These are then run in priority (100 is greater than -100.) The default checkout flow resolver, which reads from the order type's setting, runs at priority -100.

*NOTE!* Whenever you make adjustments to a module's **services.yml** you must rebuild Drupal's caches.

 

  ![](/themes/custom/mglaman/images/headshot.webp)Thanks for reading

I write about Drupal, PHP, and the quiet infrastructure behind large sites — caching, config, and testing. New posts land every week or two.

 [Subscribe via RSS →](/rss.xml) [About me →](/about-me) 

 



 

  [← Older

Drupal 8 Development Cookbook - Second Edition

 ](/blog/drupal-8-development-cookbook-second-edition) [Newer →

Why and How for SSLs and your website

 ](/blog/why-and-how-ssls-and-your-website)