oo

Implementing a Custom Order Status

You can add a custom order status by implementing the CommerceOrderStatus interface. The Commerce Order Engine provides a standard order flow out-of-the-box, but you can customize it to fit your needs.

A custom order status is a new stage added to the existing order flow. You need a custom order status when you want an order fulfillment process the standard order flow doesn’t handle. First, you’ll learn how the order statuses work, and then you’ll deploy a new example implementation.

Overview of Order Statuses

The Liferay Order Engine has six main statuses: 1) Open, 2) In Progress, 3) Pending, 4) Processing, 5) Shipped, 6) Completed.

Liferay Commerce contains six order statuses by default.

The order engine performs checks against each order status to ensure correct order processing and to determine the next status to be applied to the order. Besides the main statuses mentioned above, orders can be transitioned to three alternate statuses.

Orders can be transitioned to three alternate statuses.

  1. On Hold - An order can be put on hold when it is in any of the non-final order statuses (Pending, Processing, Shipped).

  2. Cancelled - An order can be cancelled when it is in any of the non-final order statuses (Pending, Processing, Shipped).

  3. Partially Shipped - When there are multiple items in the order and not every item has been shipped, it transitions to the Partially Shipped status.

You can add a new order status to the order flow.

You can add a custom order status to alter the out-of-the-box order flow. Below, you’ll add an order status called Scheduling and place it between the existing Pending and Processing statuses. This custom stage represents orders waiting to be scheduled before they can be accepted. A custom field on the order tracks the scheduling status. See Commerce Order Engine Overview for detailed information about each order status and its transitions.

Deploy the Order Status

  1. Start Liferay Commerce.

    docker run -it -p 8080:8080 liferay/portal:7.4.1-ga2
    
  2. Download and unzip the Acme Commerce Order Status Method.

    curl https://resources.learn.liferay.com/commerce/latest/en/developer-guide/order-management/liferay-m4v7.zip -O
    
    unzip liferay-m4v7.zip
    
  3. Build and deploy the example.

    ./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
    
    Note

    This command is the same as copying the deployed jars to /opt/liferay/osgi/modules on the Docker container.

  4. Confirm the deployment in the Docker container console.

    STARTED com.acme.m4v7.impl_1.0.0
    
  5. You must create a custom field to keep track of scheduling the order. Click the Applications Menu (Applications Menu) and navigate to Control PanelCustom Fields.

  6. Select Commerce Order from the list of items and click the Add (Add) button to add a new field. Select the Dropdown option from the available fields and enter the information below. Click Save when done.

    Field Name: m4v7Scheduling

    Data Type: Text

    Values: Pending, Confirmed (in two separate lines)

Add a custom field to keep track of scheduling the order.

  1. Verify the example order status was added by opening your browser to https://localhost:8080 and from the Applications Menu (Applications Menu), navigate to your site and place an order.

  2. Click the Applications Menu again, navigate to CommerceOrders, and select the order that you placed. A new status Scheduling and a button called Scheduling that sets the new order flow in motion appears in the order lifecycle. The new custom field is present under the Custom Fields section of the order.

    The new order status in action.

How the Custom Order Status Works

The example implementation is implemented in 3 steps. First, you must annotate the class for OSGi registration. Next, review the CommerceOrderStatus interface. Finally, finish the implementation of the custom CommerceOrderStatus.

Important

Depending on the stage where you place the new status in the order lifecycle, you must tweak the next stage for correct order processing. Since this example places the new status between the Pending and Processing statuses, you must override the existing Processing status so it checks for the new status in its logic.

Annotate the class for OSGi Registration

@Component(
	property = {
		"commerce.order.status.key=314159265",
		"commerce.order.status.priority:Integer=40"
	},
	service = CommerceOrderStatus.class
)

It is important to provide a distinct key for the order status so that Liferay Commerce can distinguish the new status from others in the order status registry. Specifying key that is already in use overrides the existing associated status. The priority of the order status determines its order in the order lifecycle. In this case, the Pending status has a priority of 30 and the processing status has a priority of 50. To place the status between the two, the priority must be between those two numbers, in this case 40.

Note

For this example implementation, a random integer is set as the key and 40 as the priority, but you can use variables for better readability within the code. See example here.

Review the CommerceOrderStatus interface

Implement the following methods:

public String getLabel(Locale locale);

This method returns the name of our order status. This name may be a language key that corresponds to the name that appears in the UI. In this case, it returns the string Scheduling.

public int getKey();

This method returns the unique key of the order status. Using an existing key overrides that status.

public int getPriority();

This method returns the priority of the order status. This is used to determine the stage at which this status is placed.

public boolean isTransitionCriteriaMet(CommerceOrder commerceOrder) throws PortalException;

This method checks if the order has met all the transition criteria specified for the current order status.

public CommerceOrder doTransition(CommerceOrder commerceOrder, long userId) throws PortalException;

Once the transition criteria for this status is met, the doTransition method performs all the actions necessary for the order to transition to this status.

public boolean isComplete(CommerceOrder commerceOrder);

This method checks if the order status has completed its action and is ready for transition to the next status. For the Scheduling status, it checks if the custom field value is equal to Pending or if it’s Confirmed and ready to transition to the Processing stage.

There are two more methods in the interface. The first one public boolean isValidForOrder(CommerceOrder commerceOrder) throws PortalException checks if the status is applicable for the order. The second one, public boolean isWorkflowEnabled(CommerceOrder commerceOrder) throws PortalException checks if there is a workflow associated with the status. There is no need to implement these methods for this example.

Complete the Order Status

The order status implementation consists of implementing the methods for the Scheduling status and also tweaking the existing business logic present in the Processing status.

Implement the isTransitionCriteriaMet method

@Override
public boolean isTransitionCriteriaMet(CommerceOrder commerceOrder)
	throws PortalException {

	if (commerceOrder.getOrderStatus() ==
			CommerceOrderConstants.ORDER_STATUS_PENDING) {

		return true;
	}

	return false;
}

For the order to transition into the Scheduling order status, it must be in the Pending status. This is checked using the getOrderStatus() method on the commerceOrder object. The method returns true if the order is pending, and false otherwise.

Implement the doTransition method

@Override
public CommerceOrder doTransition(
		CommerceOrder commerceOrder, long userId, boolean secure)
	throws PortalException {

	commerceOrder.setOrderStatus(314159265);

	return _commerceOrderService.updateCommerceOrder(commerceOrder);
}

Once the transition criteria for the order is met, it sets the order status as Scheduling using the unique key. Then it calls the updateCommerceOrder() method from _commerceOrderService, passing in the commerceOrder object to update the new status.

Implement the isComplete method

@Override
public boolean isComplete(CommerceOrder commerceOrder) {
	ExpandoBridge expandoBridge = commerceOrder.getExpandoBridge();

	String[] values = (String[])expandoBridge.getAttribute(
		"m4v7Scheduling");

	if (ArrayUtil.isEmpty(values)) {
		return false;
	}

	return Objects.equals(values[0], "Confirmed");
}

To complete the Scheduling stage, the Custom Field must be set to Confirmed. This custom attribute is retrieved through an ExpandoBridge using the key m4v7Scheduling. As it is a drop-down, the return value is present inside a String array and it’s the first value. If the value is Confirmed, the method returns true, and if the array is empty it returns false.

Override the existing Processing status

@Component(
	property = {
		"commerce.order.status.key=" + CommerceOrderConstants.ORDER_STATUS_PROCESSING,
		"commerce.order.status.priority:Integer=50",
		"service.ranking:Integer=100"
	},
	service = CommerceOrderStatus.class
)

The existing Processing status must be overridden to tweak the logic present inside it. This is done by annotating the class for OSGi registration and using the same key and priority as the existing status. A service.ranking is set to 100 for the overridden status so that it takes priority over the existing one.

Tweak the Processing status business logic

@Override
public boolean isComplete(CommerceOrder commerceOrder) {
	if (commerceOrder.isApproved() && !commerceOrder.isOpen() &&
		(commerceOrder.getOrderStatus() != 314159265)) {

		return true;
	}

	return false;
}

@Override
public boolean isTransitionCriteriaMet(CommerceOrder commerceOrder)
	throws PortalException {

	if (commerceOrder.getOrderStatus() == 314159265) {
		return true;
	}

	return false;
}

Since the original Processing status checks for the Pending status in its methods, you must tweak them slightly to check for the newly added status. This is done using the unique key of the new status.

Conclusion

Congratulations! You now know the basics for implementing the CommerceOrderStatus interface, and have added a new order status to Liferay Commerce.

Capability: