Document Properties
Kbid
301X02
Last Modified
03-Feb-2022
Added to KB
03-Feb-2022
Public Access
Everyone
Status
Online
Doc Type
Guidelines
Product
  • ICM 7.10
  • ICM 11
Guide - Custom Promotion Conditions and Parameters

Introduction

The promotion engine is a powerful marketing instrument for the shop manager. It allows to easily create a new promotion that meets the company's requirements. However, in some cases, the shop manager needs a special or individual promotion condition that is not part of the Intershop standard. For such cases, you can create a new condition yourself that fulfills the shopping experience. 

This article shows how to create a new promotion condition in a few steps and how the promotion process can be enhanced by creating user-defined parameters.

It is not necessary to copy the source code from each section. You may use the support cartridges for your own business as a blueprint:

Our demo condition is applied in the basket context. The business requirements are to check if the basket total is greater than the configured minimum order value and if the basket is marked as recurring. If both requirements are met, the condition is true. Otherwise it is false and the discount is not applied to the basket.

What do you Learn?

This guide will teach you: 

Project Cartridge Overview

Using the Component Framework for Your New Condition

First, we use the Component Framework, see Concept - Component Framework, to declare dependencies between code artifacts.

All available conditions are loaded using the component framework. Therefore create two component files for your new condition BasketRecurringOrderCondition.

promotion-condition-implementations-extensions.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
	<implementation name="BasketRecurringOrderCondition"
		class="com.intershop.support.promotion.condition.descriptor.RecurringOrderCondition" />
</components>
promotion-condition-instances-extensions.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
	<instance name="BasketRecurringOrderCondition" with="BasketRecurringOrderCondition" />
	
	<fulfill requirement="conditionDescriptor" of="promotionConditionDescriptorRegistry"
		with="BasketRecurringOrderCondition" />
</components>

Create a New Condition

We create a new condition called RecurringOrderCondition. The requirements for this PromotionConditionDescriptor are:

  • A unique ID RecurringOrderCondition

  • Specify the condition type in our scenario it is an order condition

  • Set a reference to RecurringOrderConditionEvaluator, this class will be created later

  • Set a template references (configTemplate, configErrorTemplate, displayTemplate)

  • Add the condition parameters MinOrderValue, the parameter stores the minimum order value which we need later in the evaluator

RecurringOrderCondition
package com.intershop.support.promotion.condition.descriptor;

import java.util.HashMap;

import com.intershop.beehive.core.capi.naming.NamingMgr;
import com.intershop.component.foundation.capi.condition.Condition;
import com.intershop.component.marketing.capi.promotion.condition.descriptor.PromotionConditionDescriptor;
import com.intershop.support.promotion.condition.evaluator.RecurringOrderConditionEvaluator;

public class RecurringOrderCondition extends PromotionConditionDescriptor
{
    /**
     * ID of the condition descriptor.
     */
    public static final String ID = "RecurringOrderCondition";

    public RecurringOrderCondition()
    {
        super(ID, PromotionConditionDescriptor.CONDITION_TYPE.Order);
        evaluator = new RecurringOrderConditionEvaluator();
        NamingMgr.injectMembers(evaluator);
        configTemplate = "inc/condition/RecurringOrderCondition";
        configErrorTemplate = "inc/condition/RecurringOrderConditionError";
        displayTemplate = "inc/condition/DisplayRecurringOrderCondition";
        addConditionParameters();
    }

    /**
     * Adds the parameters for <code>RecurringOrderCondition</code> to the
     * descriptor.
     * <ol>
     * <li>Minimum Order Value : <code>Integer</code></li>
     * </ol>
     */
    private void addConditionParameters()
    {
        HashMap<String, HashMap<String, String>> paramProps0 = new HashMap<>();
        HashMap<String, String> paramValidProps = new HashMap<>();
        paramValidProps.put("min", "1");
        paramValidProps.put("max", String.valueOf(Integer.MAX_VALUE));
        paramProps0.put("GlobalValidators-intrange", paramValidProps);
        addParameter("MinOrderValue", "java.lang.Integer", paramProps0, null, null,
                        "condition.error.RecurringOrderCondition.MinOrderValue");
    }

    /**
     * Returns the specified minimum order value for the condition.
     * @param condition The condition
     * @return The specified minimum order value, if not set, returns
     *         <code>null</code>
     */
    public Integer getMinOrderValue(Condition condition)
    {
        if (condition.getConditionDescriptorID().equals(ID))
        {
            return condition.getInteger("MinOrderValue");
        }
        return null;
    }    
}

Condition Templates

The creation or handling for all conditions is the same and looks like a walkthrough. It is easily possible to modify templates for the new condition that meets the business requirements.

Template configTemplate

Configures the condition parameter MinOrderValue:

configTemplate
<!--- TEMPLATENAME: RecurringOrderCondition.isml --->
<iscontent charset="UTF-8" compact="true" type="text/html"/>
<table border="0" cellpadding="0" cellspacing="0">
	<tr>
		<td class="fielditem2"><istext key="RecurringOrderCondition.MinOrderValue.fielditem"/></td>
		<td class="table_detail"><istext key="RecurringOrderCondition.MinOrderValue.table_detail"/></td>
	</tr>
	<tr>
		<td <isif condition="#ConditionForm:MinOrderValue:Invalid#">class="fielditem2_error"<iselse>class="fielditem2"</isif> nowrap="nowrap"><istext key="RecurringOrderCondition.MinOrderValue"/></td>
		<td class="table_detail">
			<input type="text" maxlength="8" size="12" class="inputfield_en"
				name="<isprint value="#ConditionForm:MinOrderValue:QualifiedName#">" 
				value="<isprint value="#ConditionForm:MinOrderValue:Value#">"
			/>
		</td>
	</tr>
</table>

Template displayTemplate

The display template for the configuration:

displayTemplate
<!--- TEMPLATENAME: DisplayRecurringOrderCondition.isml --->
<iscontent charset="UTF-8" compact="true" type="text/html">
<isif condition="#NOT(isDefined(readonly) AND (readonly EQ 'true'))#">
	<a href="<isprint value="#ConditionConfigurationURL#">" class="table_detail_link"><isprint value="#ThisDescriptor:Name#"/></a> 
<iselse>
	<isprint value="#ThisDescriptor:Name#"/> 
</isif>
</br></br>
<isif condition="#isDefined(Condition:MinOrderValue)#"> 
	<istext key="RecurringOrderCondition.MinOrderValue"/> <span class="emValue"><istext key="DisplayRecurringOrderCondition.IS.link"/></span> <isprint value="#Condition:MinOrderValue#"/>
</isif>	

Template configErrorTemplate

The error template displays an error if an invalid integer value is entered:

configErrorTemplate
<!--- TEMPLATENAME: RecurringOrderConditionError.isml --->
<iscontent charset="UTF-8">
<isif condition="#(ConditionForm:MinOrderValue:Invalid EQ 'true')#">
	<istext key="#ConditionForm:MinOrderValue:Message#"><br/>
</isif>

Localize Templates

Our templates use keys which can be localized for each language. For our demo scenario we use the en_US locale. Create the following files:

  • promotion_condition_en.properties

  • promotion_condition_auditing_en.properties

promotion_condition_en.properties
# names
condition.RecurringOrderCondition.Name=Subscription

# errors
condition.error.RecurringOrderCondition.MinOrderValue=Please, insert a valid number, which is > 1.

RecurringOrderCondition.MinOrderValue.fielditem=Condition:
RecurringOrderCondition.MinOrderValue.table_detail=Subscription
RecurringOrderCondition.MinOrderValue=Minimum Order Value:
DisplayRecurringOrderCondition.IS.link=IS


promotion_condition_auditing_en.properties
auditing.attribute.com.intershop.component.marketing.internal.promotion.condition.RebateConditionPO.conditionDescriptorID.value.RecurringOrderCondition=Cart-Abonnement

Create a New Evaluator

We have two options in which context the evaluator should work. Either on the product detail page or during the basket processing.
For our created condition, we use the basket context.

Requirements to implement the evaluator are:

  • Check if the custom basket parameter and the minimum order value are present, otherwise return false

  • Check if the basket grand total < minimum order value, otherwise return false

  • Check if the basket is marked as recurring, otherwise return false

RecurringOrderConditionEvaluator
package com.intershop.support.promotion.condition.evaluator;

import java.math.BigDecimal;
import javax.inject.Inject;
import com.intershop.beehive.bts.capi.orderprocess.LineItemCtnr;
import com.intershop.beehive.bts.internal.orderprocess.basket.BasketPO;
import com.intershop.component.application.capi.ApplicationBO;
import com.intershop.component.application.capi.CurrentApplicationBOProvider;
import com.intershop.component.basket.capi.BasketBO;
import com.intershop.component.basket.capi.BasketBORepository;
import com.intershop.component.basket.capi.extension.BasketBORecurringExtension;
import com.intershop.component.foundation.capi.condition.Condition;
import com.intershop.component.marketing.capi.promotion.evaluation.PromotionEvaluationContext;
import com.intershop.component.marketing.capi.promotion.evaluation.PromotionEvaluationObjects;
import com.intershop.component.marketing.capi.promotion.evaluation.PromotionEvaluationResult;
import com.intershop.component.marketing.capi.promotion.evaluation.PromotionFilterConditionEvaluator;
import com.intershop.component.marketing.internal.promotion.condition.evaluator.AbstractPromotionFilterConditionEvaluator;
import com.intershop.support.promotion.condition.descriptor.RecurringOrderCondition;

@SuppressWarnings("deprecation")
public class RecurringOrderConditionEvaluator extends AbstractPromotionFilterConditionEvaluator implements PromotionFilterConditionEvaluator
{    
    @Inject
    private CurrentApplicationBOProvider currentApplicationBOProvider;

    private static final String BASKET = "Basket";

    @Override
    public PromotionEvaluationResult evaluate(Condition condition,
                    PromotionEvaluationObjects promotionEvaluationObjects, int iterationCount,
                    PromotionEvaluationContext context, String filterConditionID, boolean filterConditionMandatory,
                    boolean effectDiscountLevel)
    {
        switch (context.getType())  
        {
            case BASKET:
            return evaluateBasketCondition(condition, promotionEvaluationObjects, iterationCount, context, filterConditionID, filterConditionMandatory);
        }
        return null;
    }

    private PromotionEvaluationResult evaluateBasketCondition(Condition condition,
                    PromotionEvaluationObjects promotionEvaluationObjects, int iterationCount,
                    PromotionEvaluationContext context, String filterConditionID, boolean filterConditionMandatory)
    {
        PromotionEvaluationResult promotionEvaluationResult = new PromotionEvaluationResult();
        promotionEvaluationResult.setSuccessful(false);
        // add the basket for more see Concept - Promotion Custom Parameter Passing and Cookbook - Promotions - Add Custom Parameters for the Promotion Process 
        LineItemCtnr basket = promotionEvaluationObjects.getCustomPromotionParameter(BASKET);       
        ApplicationBO applicationBO = currentApplicationBOProvider.get();
        BasketBORepository basketBORepository = applicationBO.getRepository("BasketBORepository");        
        RecurringOrderCondition orderValueCondition = ((RecurringOrderCondition)context.getConditionDescriptorRegistry().getDescriptor(condition.getConditionDescriptorID()));        

        if (orderValueCondition.getMinOrderValue(condition) == null || basket == null)
        {
            return promotionEvaluationResult;   
        }

        BasketBO basketBO = basketBORepository.getBasketBO(((BasketPO)basket).getUUID());       

        // the basket grand total is less than the minimum order value 
        if (basketBO.getGrandTotalGross().getValue().compareTo(BigDecimal.valueOf(orderValueCondition.getMinOrderValue(condition))) == -1)
        {
            return promotionEvaluationResult;  
        }

        BasketBORecurringExtension basketBORecurringExtension = basketBO.getExtension(BasketBORecurringExtension.EXTENSION_ID);

        if (basketBORecurringExtension == null)
        {
            return promotionEvaluationResult;   
        }  
        else
        {
            if (basketBORecurringExtension.isRecurring())
            {
                promotionEvaluationResult.setSuccessful(true);
            }
        }
        return promotionEvaluationResult;
    }        
}

Our new evaluator uses the method getCustomPromotionParameter(BASKET). We expect that our new condition uses a Basket for evaluation.

There are two ways to reach this goal:

  • The simplest way is to extend the parameter map. You have to override the pipeline and add the parameter Basket to the map.
  • Alternatively, you may use the pipeline extension point PrepareCustomPromotionParameters.
    Cookbook - Promotions | Recipe: Add Custom Parameters for the Promotion Process describes how to add a custom parameter for the promotion process. For this guide we use the override method to keep it simple, but we also create an extension which can be used to handle other parameters based on your own scenario.

Override the ProcessBasketCalculation Pipeline to Add the Basket Parameter for the Evaluator

Copy the pipeline ProcessBasketCalculation to your project and delete all start nodes except PreparePromotionCalculationData.

We need this start node to extend the parameter map with our current basket. The following screenshot shows the pipelet AddEntryToMap and detailed information to add the Basket to the parameter map. This is all to add a parameter for a new condition that can be handled in the context of the evaluation.

Imagine all required code artifacts were placed into the project, then the Basket parameter can be accessed by the getCustomPromotionParameter(BASKET) method invocation.

Create a New Extension Pipeline for the Pipeline Extension Point PrepareCustomPromotionParameters

Cookbook - Extension Points | Recipe: Create an Extension Pipeline describes the required steps to create a new extension pipeline. For this guide we create a new extension SupportCustomPromotion and a new pipeline HandleCustomPromotionParameters which will be invoked if the PrepareCustomPromotionParameterextension point is executed. To keep it simple the pipeline HandleCustomPromotionParameters only contains a start and an end note. Feel free to modify them for your own purposes.

Extend Existing or New Applications via Component Framework and Extend the CartridgeListProvider

The last action is to integrate our new cartridge support_bc_promotion into existing applications or new applications. Therefore we create a new file app-extentions.component:

app-extension.component
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://www.intershop.de/component/2010">
	<fulfill requirement="selectedCartridge" of="intershop.EnterpriseBackoffice.Cartridges" value="support_bc_promotion"/>
	<fulfill requirement="selectedCartridge" of="intershop.B2CBackoffice.Cartridges" value="support_bc_promotion"/>	
</components>

In as_responsive there is a file apps.component. Open this file and add our new cartridge.

The following lines are not complete but demonstrate the extension of the existing CartridgeListProvider:

apps.component
  <components xmlns="http://www.intershop.de/component/2010">

    <!-- ************************************************************************************ -->
    <!-- *           Application Type "intershop.B2CResponsive"                                * -->
    <!-- ************************************************************************************ -->
    <instance name="intershop.B2CResponsive.Cartridges" with="CartridgeListProvider">
        <fulfill requirement="selectedCartridge" value="support_bc_promotion"/>
        ...
    </instance>

Build, Deploy and Test the New Promotion Condition

To build, deploy and test the new promotion condition, perform the following steps:

  1. Open a developer console and use the build gradle tools to publish and deploy the server.
    If the server was updated then start your server and log in to Intershop Commerce Management.
  2. Switch to the channel and open the Marketing | Promotion menu and create a new promotion.
    A sample configuration looks like this: 
  3. Open the storefront and add a product to the cart.
  4. Mark the cart as subscription.
  5. Click Checkout.
    Our new promotion condition will be applied.
Disclaimer
The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.
Home
Knowledge Base
Product Releases
Log on to continue
This Knowledge Base document is reserved for registered customers.
Log on with your Intershop Entra ID to continue.
Write an email to supportadmin@intershop.de if you experience login issues,
or if you want to register as customer.