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:
-
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
-
Build and deploy the example.
./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
NoteThis command is the same as copying the deployed jars to
/opt/liferay/osgi/modules
on the Docker container. -
Confirm the deployment in the Docker container console.
STARTED com.acme.x9k1.impl_1.0.0
-
Log in as an administrator, open the Global Menu (), and click on Control Panel → Language Override. Click the Add button () and add the following keys.
Language Key Value x9k1-minimum-order-quantity X9K1 Minimum Order Quantity minimum-quantity Minimum Quantity ImportantYou 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. -
Open the Global Menu () and click on Commerce → Order Rules.
-
Click the Add button () and enter the following information:
Name: Minimum Order Quantity - 3
Description: Testing minimum order quantity of 3 items
Type: X9K1 Minimum Order Quantity
-
Click Submit.
-
In the Configuration section, set the Minimum Quantity to 3.
-
Enable the new order rule by clicking on the Active toggle.
-
Click Publish.
-
Open the Global Menu (), click on Control Panel → Sites, and add a new Minium Demo site.
-
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.
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:
- Annotate the Order Rule for OSGi Registration
- Review the
COREntryType
interface - Complete the
COREntryType
implementation - Add a display context
- Add a utility class to retrieve the minimum quantity value
- Annotate the JSP contributor for OSGi Registration
- Review the
COREntryTypeJSPContributor
interface - Complete the JSP contributor implementation
- Add a JSP to render the configuration of the Order Rule
Annotate the Order Rule for OSGi Registration
import java.util.Locale;
import org.osgi.service.component.annotations.Component;
@Component(
property = {
"commerce.order.rule.entry.type.key=x9k1-minimum-quantity-order-rule",
"commerce.order.rule.entry.type.order:Integer=1"
},
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 boolean evaluate(COREntry corEntry, List<COREntryTypeItem> corEntryTypeItems)
This method evaluates multiple items to check against the order rule and returns true or false depending on whether the condition is met. This method is not needed for this sample.
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 boolean isActive();
This method returns true to specify that the order rule is active.
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 class X9K1MinimumQuantityCOREntryTypeImpl implements COREntryType {
@Override
public boolean evaluate(COREntry corEntry, CommerceOrder commerceOrder)
throws PortalException {
if (BigDecimalUtil.gt(
BigDecimal.valueOf(_getMinimumQuantity(corEntry)),
_getOrderQuantity(commerceOrder))) {
return false;
}
return true;
}
@Override
public boolean evaluate(
COREntry corEntry, List<COREntryTypeItem> corEntryTypeItems) {
throw new UnsupportedOperationException();
}
@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 ");
Double minimumQuantity = _getMinimumQuantity(corEntry);
sb.append(minimumQuantity);
sb.append(". Add ");
Double delta = BigDecimalUtil.subtract(
BigDecimal.valueOf(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");
}
@Override
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 Double 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 Double getMinimumQuantity(COREntry corEntry) {
UnicodeProperties typeSettingsUnicodeProperties =
UnicodePropertiesBuilder.fastLoad(
corEntry.getTypeSettings()
).build();
return GetterUtil.getDouble(
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.