Custom Promotion Criteria

Promotion criteria in Ucommerce are used to determine when to trigger a discount on an order.

Ucommerce has built-in criteria for handling most use cases. However, if the need arises, it's quite easy to create a custom criterion.

Create a Custom Criterion

Creating a Definition

To create a definition, follow these steps:

  1. Create a definition and add it to your database. The definition must be of the criterion definition type.

  2. Add appropriate definition fields to it. Built-in Ucommerce data types will work out of the box.

  3. If you wish to be able to reuse the criterion for future projects we recommend you create a background service to automate this process.

Example: buy-less-than Criterion

Below is an example of setting up a criterion that triggers only when a customer is buying less than a given quantity.

public class SetupCustomCriterion : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    /// <inheritdoc />
    public SetupCustomCriterion(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    /// <inheritdoc />
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await using var asyncScope = _serviceProvider.CreateAsyncScope();
        var dbContext = asyncScope.ServiceProvider.GetRequiredService<UcommerceDbContext>();
        if (dbContext.Set<DefinitionEntity>().Any(x => x.Name == "Buy At Most"))
        {
            return;
        }

        // Set up data using dbContext
        // Find the TargetOrderLine data type
        var dataType = dbContext.Set<DataTypeEntity>()
            .FirstOrDefault(x => x.Guid.ToString() == "b5295545-5583-41e1-8c93-7bc921c09e27");
        var defFields = new List<DefinitionFieldEntity>();
        defFields.Add(new DefinitionFieldEntity
        {
            Name = "Max Amount",
            DataTypeId = 3,
            DisplayOnSite = true,
            DefaultValue = "0",
            RenderInEditor = true,
            Guid = Guid.Parse("42085d44-3e81-4d94-aad8-8699ae7d35b0")
        });

        defFields.Add(new DefinitionFieldEntity
        {
            Name = "Target Orderline",
            DefaultValue = "0",
            DisplayOnSite = true,
            RenderInEditor = true,
            DataType = dataType,
            Guid = Guid.Parse("aa793b3f-2baf-45fb-a162-cb675e8d9b64")
        });
        dbContext.Set<DefinitionEntity>()
            .Add(new DefinitionEntity
            {
                BuiltIn = false,
                Description = "buy at Most criterion definition",
                Name = "Buy At Most",
                DefinitionTypeId = 5434834, //Id of the Criterion Definition Type
                DefinitionFields = defFields,
                Guid = Guid.Parse("99ab73d5-22ee-425c-97f8-f9794ed01944")
            });

        await dbContext.SaveChangesAsync(stoppingToken);
    }
}

Using explicit GUIDs makes subsequent steps easier.

Background services are run on every startup, so it is important to have a check to prevent multiple additions of the same values.

Implementing a Pipeline Task for Satisfaction Check

To trigger a criterion we need to extend the SatisfiedCriteria pipeline with a pipeline task that checks whether the criterion is satisfied. There are four unique concepts to be aware of regarding this task:

  • CriterionDTO is a DTO connecting a criterion with its properties.

  • context.Output.CriterionDtos is a dictionary of CriterionDTO grouped by their definition.

  • Context.Output.SatisfiedOrderLineCriteria is a dictionary of lists containing criteria grouped by the order line that satisfied them. Use this for order line-level criteria.

  • context.Output.SatisfiedOrderCriteria is a list of criteria that are satisfied by the order. Use this for order-level criteria.

Remember to register your pipeline task through the pipeline builder.

Example

Here's an example of a pipeline task for checking if the buy-at-most criterion is triggered by an order. Since it can target both line- and order-level this task can handle both cases. This will not be necessary in most cases.

Validation (Optional)

It is possible to set validation rules for a criterion using FluentValidation. To make this easier, inherit from UpdateCriteriaInputValidatorBase in namespace Ucommerce.Web.BackOffice.Validators.Promotions.Criteria.UpdateCriteria to set rules for your custom criteria.

UpdateCriteriaInputValidatorBase contains the method RuleForUpdatePropertyValue(string propertyName) where propertyName is the definition field´s name.

After creating a validator it needs to be registered as a service in the program.cs file.

Example

Here's an example of custom rules for the Buy-At-Most criterion that was created earlier.

RuleForUpdatePropertyValue is case sensitive so the string must exactly match the definition field name in the database.

Tips for Reusability

To facilitate reuse of a custom criterion across projects, it is recommended to create an extension method that registers the needed services.

This is an example of a method for registering the different parts of a custom criterion:

That way, it is now possible to add the criterion to any Ucommerce solution like this:

Last updated

Was this helpful?