oo

Implementing a Custom Order Rule

You can configure Order Rules in Liferay for orders that meet a specific condition. The Minimum Order Amount rule is available out-of-the-box. It prevents checkout of orders below a specific value. To add a new Order Rule, you must implement the COREntryType interface. See Order Rules for more information.

Deploying the Custom Order Rule and Adding Language Keys

Start a new Liferay DXP instance by running

docker run -it -m 8g -p 8080:8080 liferay/dxp:2024.q1.1

Sign in to Liferay at http://localhost:8080 using the email address test@liferay.com and the password test. When prompted, change the password to learn.

Then, follow these steps:

  1. Download and unzip the Acme Commerce Order Rule.

    curl https://resources.learn.liferay.com/commerce/latest/en/developer-guide/order-management/liferay-x9k1.zip -O
    
    unzip liferay-x9k1.zip
    
  2. 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.

  3. Confirm the deployment in the Docker container console.

    STARTED com.acme.x9k1.impl_1.0.0
    
  4. Log in as an administrator, open the Global Menu (Applications Menu icon), and click on Control PanelLanguage Override. Click the Add button (Add icon) and add the following keys.

    Language Key Value
    x9k1-minimum-order-quantity X9K1 Minimum Order Quantity
    minimum-quantity Minimum Quantity
    important

    You can add language keys in the Language Override tool for Liferay DXP 7.4 U4+ or Liferay Portal 7.4 GA8+. For previous versions, you must add a Language.properties file under /src/main/resources/content/ with the keys before building and deploying.

  5. Open the Global Menu (Applications Menu icon) and click on CommerceOrder Rules.

  6. Click the Add button (Add icon) and enter the following information:

    Name: Minimum Order Quantity - 3

    Description: Testing minimum order quantity of 3 items

    Type: X9K1 Minimum Order Quantity

    Enter a name, description, and type for the custom Order Rule.

  7. Click Submit.

  8. In the Configuration section, set the Minimum Quantity to 3.

  9. Enable the new Order Rule by clicking on the Active toggle.

    Set the minimum quantity to 3 and enable the new Order Rule using the Active toggle.

  10. Click Publish.

  11. Open the Global Menu (Applications Menu icon), click on Control PanelSites, and add a new Minium Demo site.

  12. Log in as a buyer and add items to your cart. Click Submit to check out.

You can see a warning message if the order quantity is less than 3. Checkout is not possible until you meet this condition.

You can see a warning message if the order quantity is less than 3.

important

After activating an Order Rule, it applies to all Accounts, Account Groups, Order Types, and Channels. To control the eligibility, click the Order Rule’s Eligibility tab and select the appropriate option.

How the Custom Order Rule Works

This example has nine main steps:

  1. Annotate the Order Rule for OSGi Registration
  2. Review the COREntryType interface
  3. Complete the COREntryType implementation
  4. Add a display context
  5. Add a utility class to retrieve the minimum quantity value
  6. Annotate the JSP contributor for OSGi Registration
  7. Review the COREntryTypeJSPContributor interface
  8. Complete the JSP contributor implementation
  9. Add a JSP to render the configuration of the Order Rule

Annotate the Order Rule for OSGi Registration

@Component(
	property = {
		"commerce.order.rule.entry.type.key=x9k1-minimum-quantity-order-rule",
		"commerce.order.rule.entry.type.order:Integer=1"
	},
	service = COREntryType.class
)
public class X9K1MinimumQuantityCOREntryTypeImpl implements COREntryType {

You must provide a distinct key for the Order Rule so Liferay Commerce can distinguish it from others in the Order Rule registry. Specifying a key that is already in use overrides the existing associated type. The order determines its sort order in the drop-down. In this case, the order is 1, and it appears as the second item in the drop-down.

Review the COREntryType interface

Implement the following methods.

public boolean evaluate(COREntry corEntry, CommerceOrder commerceOrder) throws PortalException;

This method evaluates the Order Rule and returns true or false depending on whether the condition is met.

public String getErrorMessage(COREntry corEntry, CommerceOrder commerceOrder, Locale locale)  throws PortalException;

If the evaluated method returns false, this method returns a string containing the error message that renders a warning to the user.

public String getKey();

This method returns the unique key of the Order Rule. Using an existing key overrides that Order Rule.

public String getLabel(Locale locale);

This method returns the name of the Order Rule as it appears in the UI. This may be a language key or a string.

Complete the COREntryType implementation

public boolean evaluate(COREntry corEntry, CommerceOrder commerceOrder)
	throws PortalException {

	if (_getMinimumQuantity(corEntry) > _getOrderQuantity(commerceOrder)) {
		return false;
	}

	return true;
}

@Override
public String getErrorMessage(
		COREntry corEntry, CommerceOrder commerceOrder, Locale locale)
	throws PortalException {

	StringBundler sb = new StringBundler();

	sb.append("Order quantity is less than the minimum quantity ");

	int minimumQuantity = _getMinimumQuantity(corEntry);

	sb.append(minimumQuantity);

	sb.append(". Add ");

	int delta = minimumQuantity - _getOrderQuantity(commerceOrder);

	sb.append(delta);

	sb.append(" more item");

	if (delta > 1) {
		sb.append("s");
	}

	sb.append(" to continue.");

	return sb.toString();
}

@Override
public String getKey() {
	return "x9k1-minimum-quantity-order-rule";
}

@Override
public String getLabel(Locale locale) {
	return LanguageUtil.get(locale, "x9k1-minimum-order-quantity");
}

private int _getMinimumQuantity(COREntry corEntry) {
	return X9K1MinimumQuantityUtil.getMinimumQuantity(corEntry);
}

private int _getOrderQuantity(CommerceOrder commerceOrder) {
	int orderQuantity = 0;

	List<CommerceOrderItem> commerceOrderItems =
		commerceOrder.getCommerceOrderItems();

	for (CommerceOrderItem commerceOrderItem : commerceOrderItems) {
		orderQuantity = orderQuantity + commerceOrderItem.getQuantity();
	}

	return orderQuantity;
}

To complete the Order Rule, you must implement the above methods. There are two utility methods added to get the order quantity and the minimum quantity configured in the Order Rule. The first overridden method is evaluate() and it checks if the current order passes the Order Rule or not. It returns true if it does and false otherwise.

The second method retrieves the error message for orders that don’t satisfy the Order Rule. It returns a String converted from a StringBuilder that contains all the terms. The third method returns the unique key, and the last method returns the label that appears on the UI.

There are two additional methods to get the minimum quantity of the Order Rule and the total order quantity. The first method is present in the utility class X9K1MinimumQuantityUtil. The second method is _getOrderQuantity(CommerceOrder commerceOrder). It returns the total order quantity as a sum of individual product quantities in the order.

Add a display context

public class X9K1MinimumQuantityDisplayContext {

	public X9K1MinimumQuantityDisplayContext(COREntry corEntry) {
		_corEntry = corEntry;
	}

	public int getMinimumQuantity() {
		return X9K1MinimumQuantityUtil.getMinimumQuantity(_corEntry);
	}

	private final COREntry _corEntry;

}

The code retrieves the value for the minimum quantity configured for the Order Rule from the display context, which contains a single field of type COREntry and it is set using the created Order Rule. The display context has one method to retrieve the minimum quantity configured for the Order Rule and it uses the utility class detailed below.

Add a utility class to retrieve the minimum quantity value

public class X9K1MinimumQuantityUtil {

	public static int getMinimumQuantity(COREntry corEntry) {
		UnicodeProperties typeSettingsUnicodeProperties =
			UnicodePropertiesBuilder.fastLoad(
				corEntry.getTypeSettings()
			).build();

		return GetterUtil.getInteger(
			typeSettingsUnicodeProperties.getProperty("minimum-quantity"));
	}

}

The X9K1MinimumQuantityUtil class retrieves the minimum quantity configured for the Order Rule. It retrieves the value using the property’s name as set in the JSPkey.

Annotate the JSP contributor for OSGi Registration

@Component(
	property = "commerce.order.rule.entry.type.jsp.contributor.key=x9k1-minimum-quantity-order-rule",
	service = COREntryTypeJSPContributor.class
)
public class X9K1MinimumQuantityCOREntryTypeJSPContributor
	implements COREntryTypeJSPContributor {

The commerce.order.rule.entry.type.jsp.contributor.key property determines the Order Rule for which the JSP contributor is implemented.

Review the COREntryTypeJSPContributor interface

public void render(long corEntryId, HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws Exception;

The COREntryTypeJSPContributor interface contains one method that renders a JSP. The method requires the Order Rule’s id and objects of type HTTPServletRequest and HTTPServletResponse as arguments.

Complete the JSP contributor implementation

public void render(
		long corEntryId, HttpServletRequest httpServletRequest,
		HttpServletResponse httpServletResponse)
	throws Exception {

	COREntry corEntry = _corEntryLocalService.getCOREntry(corEntryId);

	X9K1MinimumQuantityDisplayContext x9k1MinimumQuantityDisplayContext =
		new X9K1MinimumQuantityDisplayContext(corEntry);

	httpServletRequest.setAttribute(
		WebKeys.PORTLET_DISPLAY_CONTEXT, x9k1MinimumQuantityDisplayContext);

	_jspRenderer.renderJSP(
		_servletContext, httpServletRequest, httpServletResponse,
		"/minimum_quantity.jsp");
}

@Reference
private COREntryLocalService _corEntryLocalService;

@Reference
private JSPRenderer _jspRenderer;

@Reference(target = "(osgi.web.symbolicname=com.acme.x9k1.impl)")
private ServletContext _servletContext;

To complete the JSP Contributor, you must implement the render() method. It retrieves the COREntry using the _corEntryLocalService and the corEntryId. Then it creates a new display context of type X9K1MinimumQuantityDisplayContext using the retrieved corEntry. This context is set to the httpServletRequest. The servletContext references the Bundle-Symbolic-Name from the bnd.bnd file. The JSPRenderer renders a JSP file with the renderJSP() method. It accepts the relative path of the JSP, servletContext, httpServletRequest, and httpServletResponse as arguments.

Add a JSP to render the configuration of the Order Rule

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %><%@
taglib uri="http://liferay.com/tld/commerce-ui" prefix="commerce-ui" %>

<%@ page import="com.acme.x9k1.internal.commerce.order.rule.web.display.context.X9K1MinimumQuantityDisplayContext" %>

<%@ page import="com.liferay.portal.kernel.util.WebKeys" %>

<%
X9K1MinimumQuantityDisplayContext x9k1MinimumQuantityDisplayContext = (X9K1MinimumQuantityDisplayContext)request.getAttribute(WebKeys.PORTLET_DISPLAY_CONTEXT);
%>

<div class="row">
	<div class="col">
		<commerce-ui:panel
			bodyClasses="flex-fill"
			title="Configuration"
		>
			<div class="row">
				<div class="col">
					<aui:input label="minimum-quantity" name="type--settings--minimum-quantity--" required="<%= true %>" type="text" value="<%= x9k1MinimumQuantityDisplayContext.getMinimumQuantity() %>">
						<aui:validator name="number" />
					</aui:input>
				</div>
			</div>
		</commerce-ui:panel>
	</div>
</div>

The JSP contains one input field to accept the minimum quantity for the Order Rule. It is retrieved through the display context and evaluated inside the custom Order Rule. The display context uses the utility class and fetches the field using the minimum-quantity name from the type settings configuration. The getMinimumQuantity() method retrieves the existing value if any.

Conclusion

Congratulations! You now know the basics for implementing the COREntryType interface and have added a new Order Rule to Liferay Commerce.

Capability: