oo

Adding a New Discount Rule Type

You can add a new discount rule type by implementing two interfaces: CommerceDiscountRuleType and CommerceDiscountRuleTypeJSPContributor.

Discount rule types define conditions for evaluating when discounts are applied to an order. Liferay provides three discount rule types out-of-the-box: AddedAllCommerceDiscountRuleTypeImpl, AddedAnyCommerceDiscountRuleTypeImpl, and CartTotalCommerceDiscountRuleTypeImpl.

Discount rule types available out-of-the-box.

Deploy an Example Discount Rule Type

Start a new Liferay instance by running

docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.112-ga112

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

Then, follow these steps to deploy an example discount rule type on your Liferay instance:

  1. Download and unzip the Acme Commerce Discount Rule Type.

    curl https://resources.learn.liferay.com/commerce/latest/en/developer-guide/promotions/liferay-m6a8.zip -O
    
    unzip liferay-m6a8.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 Liferay Docker container console.

    STARTED com.acme.m6a8.web_1.0.0
    
  4. Verify that the example discount rule type was added. Open your browser to https://localhost:8080. Then click the Global Menu (Global Menu) and navigate to CommerceDiscounts. Click Edit within the menu for any discount. Scroll down to the Rules section and click Add (Add) to add a new discount rule. The new discount rule type (“Has a minimum number of products”) is present under the Type dropdown.

Note

In Commerce 2.1 and earlier, you can find discounts by navigating to Control PanelCommerceDiscounts. Click any Edit within the menu for any discount and then click Rules at the top of the screen.

Verify the addition of a new discount rule type

Congratulations, you’ve successfully built and deployed a new discount rule type that implements CommerceDiscountRuleType.

Anatomy of a Discount Rule Type

There are two classes: a discount rule type class and a JSP contributor for a custom UI input. Follow these steps:

Annotate the Discount Rule Type Class for OSGi Registration

@Component(
	property = {
		"commerce.discount.rule.type.key=m6a8",
		"commerce.discount.rule.type.order:Integer=51"
	},
	service = CommerceDiscountRuleType.class
)
public class M6A8CommerceDiscountRuleTypeImpl
	implements CommerceDiscountRuleType {

It is important to provide a distinct key for the discount rule type so that Liferay can distinguish the new type from others in the discount rule type registry. Declaring a key that is already in use overrides the existing associated type.

The commerce.discount.rule.type.order value indicates how far in the list of available discount rule types this type appears. For example, the “added all” discount rule type has a value of 50. Giving your discount rule type a value of 51 ensures that it appears immediately after the “added all” type.

Review the CommerceDiscountRuleType Interface

Implement the following methods:

public boolean evaluate(
		CommerceDiscountRule commerceDiscountRule,
		CommerceContext commerceContext)
	throws PortalException {

This method is where you implement the business logic for evaluating when the discount rule is applied.

public String getKey();

This provides a unique identifier for the discount rule type in the discount rule type registry. The key can be used to fetch the new type from the registry.

public String getLabel(Locale locale);

This returns a text label that describes how the discount rule is applied. See the implementation in M6A8CommerceDiscountRuleTypeImpl.java for a reference in retrieving the label with a language key.

Annotate the JSP Contributor Class for OSGi Registration

@Component(
	property = "commerce.discount.rule.type.jsp.contributor.key=m6a8",
	service = CommerceDiscountRuleTypeJSPContributor.class
)
public class M6A8CommerceDiscountRuleTypeJSPContributor
	implements CommerceDiscountRuleTypeJSPContributor {

It is important to provide a distinct key for the JSP contributor so that Liferay can distinguish the contributor from others in the discount rule type JSP contributor registry. Declaring a key that is already in use overrides the existing associated type.

Review the CommerceDiscountRuleTypeJSPContributor Interface

Implement the following method:

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

This is where the code to render a custom UI input for our discount rule type goes.

Complete the Discount Rule Type

The discount rule type comprises of the backend logic for evaluating when to apply a discount rule to an order, logic to render UI inputs for the discount rule type, and the custom UI inputs themselves.

Configure the ServletContext for the Module

Define the ServletContext in the JSP contributor class using the bundle’s symbolic name so it can find the JSP:

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

The value set for osgi.web.symbolicname matches the value for Bundle-SymbolicName in the bnd.bnd file. These values must match for the ServletContext to locate the JSP.

Declare a unique value for Web-ContextPath in the bnd.bnd file so the ServletContext is correctly generated. In this example, Web-ContextPath is set to /m6a8-web. See the bnd.bnd file for a reference on these values.

Implement the CommerceDiscountRuleTypeJSPContributor’s render Method

	@Override
	public void render(
			long commerceDiscountId, long commerceDiscountRuleId,
			HttpServletRequest httpServletRequest,
			HttpServletResponse httpServletResponse)
		throws Exception {

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

Use a JSPRenderer to render the JSP for the discount rule type’s custom UI input (in our example, view.jsp). Provide the ServletContext as a parameter to find the JSP.

Add the Evaluation Logic to evaluate

@Override
public boolean evaluate(
		CommerceDiscountRule commerceDiscountRule,
		CommerceContext commerceContext)
	throws PortalException {

	CommerceOrder commerceOrder = commerceContext.getCommerceOrder();

	if (commerceOrder == null) {
		return false;
	}

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

	int mininumNumberOfItems = GetterUtil.getInteger(
		commerceDiscountRule.getSettingsProperty(
			commerceDiscountRule.getType()));

	if (commerceOrderItems.size() >= mininumNumberOfItems) {
		return true;
	}

	return false;
}

Implement any conditions here that must be true for a discount rule to be applied. This example checks that the order contains at least a minimum number of items, using a minimum value defined by a custom UI input (stored as a String within the CommerceDiscountRule).

The CommerceOrder object represents information about the order being evaluated. See CommerceOrder.java and CommerceOrderModel.java to find more information you can get from a CommerceOrder.

Add a JSP to Render the Custom UI Input

The example uses a JSP called view.jsp with a numeric input for a minimum number of products.

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

<aui:input label="minimum-number-of-items" name="typeSettings" type="text">
	<aui:validator name="digits" />
	<aui:validator name="min">1</aui:validator>
</aui:input>

Implement UI elements to present when defining a discount rule. These appear immediately after selecting the discount rule type. Defining an input causes the saved value to be stored in the discount rule’s settings properties.

See Using AUI Taglibs for more information on using AUI inputs.

Add the Language Keys to Language.properties

Add the language keys and their values to a Language.properties file:

has-a-minimum-number-of-items=Has a Minimum Number of Items
minimum-number-of-items=Minimum Number of Items

See Localizing Your Application for more information.

Conclusion

Congratulations! You now know the basics for implementing the CommerceDiscountRuleType interface, and have added a new discount rule type with a custom UI input to Liferay.

Capability: