Sitecore Experience Commerce: Implementing Multi-Step Actions in the Business Tools

In this article, we will look at multi-step actions and how we can implement them for customising the Sitecore Commerce Business Tools.

What is a Multi-Step Action?

The multi-step action is the approach to building out entity view modals to act as a kind of wizard, where inputs from each step can affect the subsequent steps.

Adding a qualification/condition to a promotion - step 1.
Adding a qualification/condition to a promotion - step 2.

A Dive into the Multi-Step Action Implementation

We will take a dive into the pieces that make up the multi-step action implementation to see just how we should be building our custom multi-step modal, using the Add Qualification modal for promotions as our working example.

This section is focused on explaining what will be found in the current platform implementation, which can be useful if looking to extend the BizFx and Commerce Engine functionality, but can be skipped if you are only after Implementing Custom Multi-Step Actions as quick as possible.

MultiStepActionPolicy

The MultiStepActionPolicy houses a single property, FirstStep, which will contain the EntityActionView that will be populated in the modal entity view.

public MultiStepActionPolicy()
{
    this.FirstStep = new EntityActionView();
}

public EntityActionView FirstStep { get; set; }

In the following code snippet, we see that the MultiStepActionPolicy is added to the EntityActionView, in which the QualificationsDetails that is assigned to the EntityView will use to populate the first step of the add qualification modal.

Line 18 highlights the Name of the entity view action, which will be resolved to its localised value from the .../Commerce Terms/BusinessTools/ViewActionNames/AddQualification, and populates modal's title.

var actionPolicy = arg.GetPolicy<ActionsPolicy>();
actionPolicy.Actions.Add(
        new EntityActionView(new List<Policy>
        {
            new MultiStepActionPolicy
            {
                FirstStep = new EntityActionView
                {
                    Name = context.GetPolicy<KnownPromotionsActionsPolicy>().SelectQualification,
                    DisplayName = "Select Qualification",
                    Description = "Selects a Qualification",
                    IsEnabled = isEnabled,
                    EntityView = context.GetPolicy<KnownPromotionsViewsPolicy>().QualificationDetails
                }
            }
        })
        {
            Name = context.GetPolicy<KnownPromotionsActionsPolicy>().AddQualification,
            DisplayName = "Add Qualification",
            Description = "Adds a Qualification",
            IsEnabled = isEnabled,
            EntityView = string.Empty,
            Icon = "add"
        });
The entity view populated in the first step, of the Add Qualification modal, is resolved from the entity view action name of the EntityActionView, assigned to the MultiStepActionPolicy's FirstStep, which in this case is the SelectQualification action.

LocalizeEntityViewBlock

In the LocalizeEntityViewBlock, we find that the SelectQualification action name is being populated with the localised term, however as mentioned above, the modal utilises the AddQualification localised term instead, therefore we don't need to be concerned with this.

if (!action.HasPolicy<MultiStepActionPolicy>())
{
    continue;
}

var firstStepAction = action.GetPolicy<MultiStepActionPolicy>().FirstStep;
await SetActionLocalizedTerms(firstStepAction, context).ConfigureAwait(false);

MultiStepActionModel

This model is quite simple. The NextStep represents the name of the action to be executed when the modal form is submitted.

public MultiStepActionModel(string nextStep)
{
    this.NextStep = nextStep;
}

public string NextStep { get; set; }

In the following extract of DoActionSelectQualificationBlock, I have substituted out most of the code with comments, explaining the original code logic, to reduce the noise and keep focus on the multi-step implementation functionality.

In line 11, we see that the original condition property has been made readonly as we don't want the user to change their mind at this stage, however we don't want to hide the property as the user still needs to have context of what was previously selected.

Updating the readonly status of submitted view properties is considered a recommended practice.

Line 15 we have a comment that tells us that this is where the next step's view properties are added to the current entity view, building out the modal form.

This also means that the Do Action Blocks acts a pseudo Get Entity View Block as the initial modal's entity view is populated via the GetEntityView() API, utilising the IGetEntityViewPipeline under the hood, while subsequent updates in the multi-step implementation are triggered as the DoUxAction() API calls the IDoActionPipeline to process requests.

Finally, line 17 has the original entity view action name, 'AddQualification', added to the MultiStepActionModel as the NextStep and applied to the commerce context.

With the knowledge that adding the MultiStepActionModel to the context effectively creates the next step in the modal, there is no limit to how many steps we can add to a multi-step modal.

public override async Task<EntityView> Run(EntityView entityView, CommercePipelineExecutionContext context)
{
    /* validate action */

    /* validate promotion */

    var selectedCondition = entityView.Properties.FirstOrDefault(p => p.Name.Equals("Condition", StringComparison.OrdinalIgnoreCase));

    /* validate condition */

    selectedCondition.IsReadOnly = true;

    /* add and/or conditional operator. hide if no qualifications have been applied to the promotion so far */

    /* add new view properties for selected condition */

    context.CommerceContext.AddModel(new MultiStepActionModel(context.GetPolicy<KnownPromotionsActionsPolicy>().AddQualification));

    return entityView;
}
DoActionSelectQualificationBlock validates the Condition selection and updates the entity view to contain the remaining view properties required for configuring the 'Cart Has [count] Items?' qualification as the second step of this multi-step action modal.

CheckForMultiStepActionBlock

From the following snippet from CheckForMultiStepActionBlock, we see that it updates the current entity views action, removes the MultiStepActionModel from the commerce context, and adds the entity view to the commerce context.

In short, this logic is more of a helper, and we could get the same result by omitting the registration of the MultiStepActionModel, updating the entity view action, and add the entity view directly in the Do Action Block instead.

var multiAction = context.CommerceContext.GetModels<MultiStepActionModel>().FirstOrDefault();
if (string.IsNullOrEmpty(multiAction?.NextStep))
{
    return Task.FromResult(arg);
}

entityView.Action = multiAction.NextStep;
context.CommerceContext.RemoveModel(multiAction);
context.CommerceContext.AddModel(entityView);

The DoUxAction API

The last piece of the puzzle comes with BizFx's handling of the response of the DoUxAction API when we submit the modal dialog.

If an entity view object is returned in the API response object, BizFx will render the modal with the updated view instead of closing the modal and refreshing the current page view.

If we were to change the modal's entity view name during any of the steps it will not be reflected in the modal's title as there is no handling for this implemented by default in BizFx.

Summary

Let's review what we have learnt about multi-step actions.

  1. Adding the MultiStepActionPolicy to an EntityActionView basically swaps out the intended action from being executed in the initial GetEntityView() request, however the modal's title will reflect the initial EntityActionView's localised name.
  2. The LocalizeEntityViewBlock application to the EntityActionView in the MultiStepActionPolicy's FirstStep is superfluous and does not impact the multi-step implementation.
  3. Do Action Blocks act as pseudo Get Entity View Block for updating the modal's entity view.
  4. When updating entity views with multi-step actions, previously input view properties should be set to readonly as a recommended practice.
  5. The usage of the MultiStepActionModel is more of a helper model in CheckForMultiStepActionBlock, rather than a dependent piece of the implementation.
  6. There is no limit to how many steps we can add to a multi-step action modal.
  7. The modal title is not updated with an updated entity view.
  8. When the Commerce Engine's DoUxAction() API returns an entity view in the response model, BizFx will render it in the current modal view.

Implementing Custom Multi-Step Actions

For implementing custom multi-step actions we will skip the details about how to build out the pre-requisites, being the initial Populate View Actions Block to create the entity view action that will trigger the modal, and the Get Entity View Block that will populate the initial entity view that is rendered in the modal, and instead focus on the Do Action Block that will allow us to add the additional steps to the modal.

The multi-step sample show the initial entity view that we will create the second step for.

Essentially, we can implement multi-step actions without using any of the MultiStep classes, simply by adding the new entity view to the commerce context during a Do Action Block, however providing a more complete sample, the following code logic would be applied to our custom Do Action Block.

  1. Validate action
  2. Validate entity (if applicable)
  3. Validate current view properties
  4. Set current view properties to read only
  5. Add new view properties to entity view for next step
  6. Update entity view action with action to perform on the next time the modal is submitted
  7. Add entity view to the commerce context
public override async Task<EntityView> Run(EntityView entityView, CommercePipelineExecutionContext context)
{
    Condition.Requires(entityView).IsNotNull($"{Name}: The argument cannot be null");

    /* 1. Validate action */
    if (string.IsNullOrEmpty(entityView?.Action)
        || !entityView.Action.Equals("FirstAction", StringComparison.OrdinalIgnoreCase))
    {
        return await Task.FromResult(entityView).ConfigureAwait(false);
    }

    /* 2. Validate entity (if applicable) */
    // Not applicable

    /* Validate current view properties */
    // Not critical for sample implementation

    /* Set current view properties to read only */
    foreach (var property in entityView.Properties)
    {
        property.IsReadOnly = true;
    }

    /* Add new view properties to entity view for next step */
    entityView.Properties.Add(new ViewProperty
    {
        Name = "Step 2 Field 1"
    });

    entityView.Properties.Add(new ViewProperty
    {
        Name = "Step 2 Field 2",
        IsRequired = false
    });

    /* Update entity view action with action to perform on the next time the modal is submitted */
    entityView.Action = "SecondAction";

    /* Add entity view to the commerce context */
    context.CommerceContext.AddModel(entityView);

    return await Task.FromResult(entityView).ConfigureAwait(false);
}
Our multi-step sample entity view after the modal has been submitted, creating the second step view for completing data input.

Sitecore Experience Commerce: Configuring and Customising the BizFx Solution with Locale Support

In this article, we will look at how the locale can be changed in BizFx to support a desired locale type and the steps required to add support for locales that haven't been configured in the BizFx solution.

Working in Australia, a common customisation required is to add the 'en-AU' locale, primarily to support date/time formatting in BizFx.

A sample date using the default 'en' locale configuration.
The same sample date using the 'en-AU' locale.

Adding Support for a Locale

To add support for a locale that has not been registered to BizFx, the process is quite simple.

If you are looking to configure BizFx with any of the following locales, then this step will not be required. Default Locales: 'en' ('en-US'), 'fr-FR', 'de-DE', and 'Ja-JP'.

In the BizFx solution, open src\app\app.module.ts , import the locale and advanced formatting options from the extra dataset and register the locale via the registerLocaleData function.

The locales are already provided with the BizFx SDK, under src\locales.

/* Locales */
import localeFr from '../locales/fr';
import localeFrExtra from '../locales/extra/fr';
import localeJa from '../locales/ja';
import localeJaExtra from '../locales/extra/ja';
import localeDe from '../locales/de';
import localeDeExtra from '../locales/extra/de';
import localeEnAu from '../locales/en-AU';
import localeEnAuExtra from '../locales/extra/en-AU';

registerLocaleData(localeFr, 'fr-FR', localeFrExtra);
registerLocaleData(localeDe, 'de-DE', localeDeExtra);
registerLocaleData(localeJa, 'Ja-JP', localeJaExtra);
registerLocaleData(localeEnAu, 'en-AU', localeEnAuExtra);

Once we have registered the locale, if we were to configure the locale at this point we would find that there is some messaging in that is not rendered quite right. This is because there are also a handful of text overrides that are stored in the internationalisation folder of BizFx ('src\assets\i18n\') that also need to be added to override the default messaging in BizFx.

The LanguageSelector display text has not been configured.

Copy an existing locale file fom the internationalisation folder and update the messaging accordingly.

{
    "ValidationErrors": {
        "IsRequired": "{{DisplayName}} is required.",
        "IncorrectDecimalValue": "{{DisplayName}} has an incorrect decimal value.",
        "IncorrectDateValue": "{{DisplayName}} has an incorrect date value."
    },
    "ActionExecuted": "{{DisplayName}} executed.",
    "Back": "Back",
    "Logout": "Log out",
    "LanguageSelector": "Language displayed",
    "NoSearchResults": "No results matching your search were found.",
    "Searching": "Searching..."
}
The LanguageSelector display text when configured for 'en-AU'.

Configuring the Locale for BizFx

Now that the locale has been registered to the BizFx solution, the next step is to update the BizFx configuration to use it.

Open src\assets\config.json and set the Language to the newly registered locale.

{
  "EnvironmentName": "HabitatAuthoring",
  "EngineUri": "https://commerceauthoring.XC92.local",
  "IdentityServerUri": "https://XP0.IdentityServer",
  "BizFxUri": "https://bizfx.XC92.local",
  "Language": "en-AU",
  "Currency": "USD",
  "ShopName": "CommerceEngineDefaultStorefront",
  "LanguageCookieName": "selectedLanguage",
  "EnvironmentCookieName": "selectedEnvironment",
  "AutoCompleteTimeout_ms": 300
}

Deploy the BizFx solution as per your preferred deployment method and the locale will now be utilised throughout the BizFx tooling.

Source Code

The source code for this example of adding the 'en-AU' locale and other BizFx customisations can be found at Ajsuth.BizFx.

Sitecore Experience Commerce: Associating Inventory from Sellable Item and Variant Pages

In this article, we introduce a small custom plugin for Sitecore Commerce Business Tools that enables the inventory association for sellable items and variants directly from the Merchandising Manager pages.

Establishing inventory information associations is an action hosted within the Inventory Manager. Prior to the introduction of inventory indexes in XC 9.3, locating an existing inventory information record for large catalogs via the Inventory Manager can be quite a tedious task with the pagination controls being the only form of search.

The custom plugin adds the Associate Sellable Item to an inventory set action to the Inventory Sets entity view for the Sellable Item and Variant merchanding pages.

A transitional step to select the Inventory Set is added to the modal view, which in the Inventory Manager is driven by the inventory set being viewed.

The final step locks the Inventory Set and Sellable Item fields, as they have already been identified, and if the sellable item already had been associated to the selected inventory set then this would behave like an edit view, keeping the UX at the forefront of this implementation.

Managing existing inventory information records can also be more achieved via the Sellable Item and Variant pages in the Inventory Sets entity view actions, however prior to XC 9.3 a business was required to create a new entity version to enable these controls, unless the EntityVersionsActionsPolicy had been updated to allow these actions to bypass the entity version (as they should). This process was also documented in Enabling Disassociate, Edit, and Transfer Inventory Actions for Published Sellable Items and Variants.

Source Code: Extended Sitecore Commerce Inventory project repository

Sitecore Identity Server: Increasing the Token Lifetime for Local Development

In this article, we will review how to change the authentication token timeout values that force us to log back in to Sitecore or request a new token from Postman. If you are like me, generally working with Sitecore/Sitecore Commerce 10+ hours per day, 6 days a week, it can seem like you are kicked out every 5 minutes. Personally, I set these timeouts to a week (604800 seconds).

Changing the timeouts are not recommended for production instances.

Changing the Timeouts in Sitecore Identity Server

Sitecore Identity Server was first introduced with Sitecore Commerce 9.0.0 and with the release of Sitecore 9.1, Sitecore Identity Server was added to Sitecore authentication process.

Updating the Token Lifetimes in 9.0.X

  1. Open <Sitecore Identity Server root>\wwwroot\appsettings.json.
  2. Under AppSettings.Clients, update the CommerceBusinessTools and Postman API clients for BizFx and Postman applications respectively :-
    1. Update the AccessTokenLifetimeInSeconds and IdentityTokenLifetimeInSeconds from the default 3600 (seconds) to the desired timespan, in seconds.
    2. Save the configuration.
  3. Restart the Sitecore Identity Server so that the updated configuration is consumed on startup.

Updating the Token Lifetimes in 9.3

  1. Open <Sitecore Identity Server root>\Config\production\Sitecore.Commerce.IdentityServer.Host.xml.
  2. Under /Settings/Sitecore/IdentityServer/Clients, update the CommerceClient and PostmanClient for BizFx and Postman applications respectively:-
    1. Update the AccessTokenLifetimeInSeconds and IdentityTokenLifetimeInSeconds from the default 3600 (seconds) to the desired timespan, in seconds.
    2. Save the configuration.
  3. Restart the Sitecore Identity Server so that the updated configuration is consumed on startup.

Sitecore Experience Commerce: Working with Digital Sellable Items – Part 2

In this article, we look closer at the techincal details behind how digital sellable items are managed in the Commerce Engine, which in turn drives storefront functionality.

For an introduction to digital sellable items and how to configure them, see Working with Digital Sellable Items - Part 1.

Technical Implementation Details

See the Terminology Definitions for further explanation of tag types and Digital Item Tag Types for the default tags available, which are used throughout this article.

Inventory

Digital sellable items are always available for purchase, which I think of as having perpetual inventory. This functionality is driven by the AvailabilityAlwaysPolicy.

During the IGetSellableItemPipeline, the EnsureSellableItemAvailabilityPoliciesBlock replaces the sellable item's AvailabilityPolicy with the AvailabilityAlwaysPolicy where one or more of the sellable item's tags match the digital item tags.

See Digital Item Tags for the full list of default tags.

Plugin.Catalog.IGetSellableItemPipeline
  Plugin.Catalog.PrepGetSellableItemBlock
  Core.IFindEntityPipeline
  Plugin.Catalog.FilterSellableItemVariantsBlock 
  Plugin.Catalog.FormatComposerViewPropertyBlock 
  Plugin.Catalog.IPostGetSellableItemPipeline 
    Plugin.Catalog.GetSellableItemCatalogBlock 
    Plugin.Catalog.ICalculateSellableItemPricesPipeline
    Plugin.Catalog.AddSellableItemToContextBlock
    Plugin.Availability.EnsureSellableItemAvailabilityPoliciesBlock
    Plugin.Availability.IPopulateItemAvailabilityPipeline

Inventory Rules - Standalone Sellable Item

Summary: The AvailabilityAlwaysPolicy (perpetual inventory) is dependent on digital item tags being configured on the sellable item.

Inventory Rules - Sellable Items with Variants

Summary: The AvailabilityAlwaysPolicy (perpetual inventory) is dependent on the digital item tags being configured on the sellable item, which propagates to all variants. Configuring digital item tags on the variants will not have any effect, therefore perpetual inventory will either apply to all variants or no variants.

Cart Lines

When adding an item to the cart, the IAddCartLinePipeline runs, with the notable registered pipeline block or pipeline is IPopulateValidateCartPipeline, which syncs cart lines with the data from the sellable item entities. In part this is achieved with by calling the IGetSellableItemPipeline (more detail above in Inventory) for each cart line, which is important as the subsequent blocks that utilise the presence of AvailabilityAlwaysPolicy.

Plugin.Carts.IAddCartLinePipeline
  Plugin.Catalog.ValidateSellableItemBlock
  Plugin.Carts.AddCartLineBlock
  Plugin.Carts.AddContactBlock
  Plugin.Carts.IPopulateValidateCartPipeline
  Plugin.GiftCards.AddCartLineGiftCardBlock
  Plugin.DigitalItems.AddCartLineDigitalProductBlock
  Plugin.DigitalItems.AddCartLineWarrantyBlock
  Plugin.DigitalItems.AddCartLineInstallationBlock
  Plugin.Carts.ICalculateCartLinesPipeline
  Plugin.Carts.ICalculateCartPipeline
  Plugin.Carts.PersistCartBlock

These subsequent pipeline blocks, AddCartLineDigitalProductBlock, AddCartLineWarrantyBlock, and AddCartLineInstallationBlock, determine if the newly added cart line is a digital item type by comparing the cart line tags against its respective set of digital item type tag lists in conjunction with the AvailabilityAlwaysPolicy. Similarly, the AddCartLineGiftCardBlock also compares cart line tags against gift card tags, but ignores the presence of the AvailabilityAlwaysPolicy.

If the cart line has been identified as a digital sellable item, it will be split the cart line out into single quantity lines, so that each digital sellable item can have unique fulfillment details, coming up in the checkout's delivery step.

Plugin.Carts.IAddCartLinePipeline
  Plugin.Catalog.ValidateSellableItemBlock
  Plugin.Carts.AddCartLineBlock
  Plugin.Carts.AddContactBlock
  Plugin.Carts.IPopulateValidateCartPipeline
  Plugin.GiftCards.AddCartLineGiftCardBlock
  Plugin.DigitalItems.AddCartLineDigitalProductBlock
  Plugin.DigitalItems.AddCartLineWarrantyBlock
  Plugin.DigitalItems.AddCartLineInstallationBlock
  Plugin.Carts.ICalculateCartLinesPipeline
  Plugin.Carts.ICalculateCartPipeline
  Plugin.Carts.PersistCartBlock

Cart Line Rules - Standalone Sellable Item

Summary: Configuring a sellable item with one or more digital item type tags and one or more digital item tags (triggering perpetual inventory) will split cart lines into single quantity lines. Where one or more gift card tags are configured to a sellable item, this will also split cart lines and does not require a digital item tag configured.

Cart Line Rules - Sellable Items with Variants

Summary: Similar to sellable item configurations, the only difference for configuring sellable items with variants is that the digital item tags or gift card tags need to apply to the variants themselves.

Consideration: Variants inherit its parent sellable item's tags if not configured, however if a tag is configured on a variant it will not inherit the tags from the parent sellable item.

* includes tags inherited from parent sellable item

Cart Fulfillment Option Types

For the checkout's delivery step, the IGetCartFulfillmentOptionsPipeline is executed within the GetCartFulfillmentOptions API, returning the available fulfillment option types for the storefront.

Plugin.Fulfillment.IGetCartFulfillmentOptionsPipeline
  Plugin.Fulfillment.FilterCartFulfillmentOptionsBlock

The key pipeline block is the FilterCartFulfillmentOptionsBlock, which roughly provides the following logic.

  1. Retrieves all fulfillment option types associated to the storefront.
  2. Removes the "SplitShipping" (Deliver Items Individually - split shipment) type if there is only one cart line.
  3. Removes the physical fulfillment option type, "ShipToMe" (Ship To Address), where a cart contains digital sellable items, identified by the AvailabilityAlwaysPolicy.
  4. Removes the digital fulfillment option type, "Digital" (Digital Delivery), where a cart contains physical sellable items.

The storefront's fulfillment options are located in the Sitecore Content Editor at /sitecore/Commerce/Commerce Control Panel/Storefront Settings/Storefronts/<storefront>/Fulfillment Configuration.

Cart Fulfillment Rules - Standalone Sellable Item

Summary: The digital fulfillment option types are dependent on the presence of the AvailabilityAlwaysPolicy (perpetual inventory) on the sellable item, rather than comparing the digital item type or gift card tags as one would expect.

Cart Fulfillment Rules - Sellable Items with Variants

Summary: The digital fulfillment option types are dependent on the presence of the AvailabilityAlwaysPolicy (perpetual inventory) on the sellable item, rather than comparing the digital item type or gift card tags on the variants as one would expect.

Consideration: This is also another instance where the parent sellable item drives the behaviour of all of its variants, so if you selling books, for example, and wanted to sell the digital PDF version, there is a customisation here to think about.

Cart Line Fulfillment Option Types

Where the split shipping option type is selected during the checkout's delivery step, each cart line will require their own fulfillment option type, of which the available types per cart line are determined via the GetCartLineFulfillmentOptions API.

Plugin.Fulfillment.IGetCartLineFulfillmentOptionsPipeline
  Plugin.Fulfillment.FilterCartLineFulfillmentOptionsBlock

The key pipeline block in this pipeline is the FilterCartLineFulfillmentOptionsBlock, which has similar logic to the FilterCartFulfillmentOptionsBlock, but focuses on each cart line in isolation.

  1. Retrieves all fulfillment option types associated to the storefront.
  2. Removes the "SplitShipping" / (Deliver Items Individually - split shipment) type as this option is not applicable for individual line items.
  3. Removes the physical fulfillment option type, "ShipToMe" (Ship To Address), where the cart line is a digital sellable item, identified by the "entitlement" tag.
  4. Removes the digital fulfillment option type, "Digital" (Digital Delivery), where the cart line is a physical sellable item.

You may have noticed that the "entitlement" tag is being utilised instead of the AvailabilityAlwaysPolicy as per the FilterCartFulfillmentOptionsBlock. Although the "entitlement" tag does exist in the Digital Item Tags, effectually meaning that the sellable item will still contain the AvailablityAlwaysPolicy, it's important note to take for identifying how digital sellable items need to be configured, so we don't run into any surprises in the storefront.

Cart Line Fulfillment Rules - Standalone Sellable Item

Summary: The digital fulfillment option types for cart lines are dependent on the presence of the "entitlement" tag on the sellable item, which by default is equivalent to the AvailabilityAlwaysPolicy, rather than comparing the digital item type or gift card tags as one would expect.

Cart Line Fulfillment Rules - Sellable Items with Variants

Summary: The digital fulfillment option types for cart lines are dependent on the presence of the "entitlement" tag on the variant, which by default is equivalent to the AvailabilityAlwaysPolicy, rather than comparing the digital item type or gift card tags as one would expect.

* includes tags inherited from parent sellable item

Entitlements

Entitlements are provisioned during the released order minion. The GenerateOrderEntitlementsBlock is the central point for creating the entitlements, calling the IProvisionEntitlementsPipeline to handle each digital item type.

Plugin.Orders.IReleasedOrdersMinionPipeline
  Plugin.Fulfillment.GenerateOrderShipmentBlock
  Plugin.Fulfillment.GenerateOrderLinesShipmentBlock
  Plugin.Entitlements.GenerateOrderEntitlementsBlock
  Plugin.FaultInjection.MinionFaultBlock
  Plugin.FaultInjection.SettlePaymentFaultBlock
  Plugin.Sample.Payments.Braintree.SettleOrderSalesActivitiesBlock
  Plugin.Orders.MoveReleasedOrderBlock
Plugin.Entitlements.IProvisionEntitlementsPipeline
  Plugin.Entitlements.ProvisionEntitlementsBlock
  Plugin.GiftCards.ProvisionGiftCardEntitlementsBlock
  Plugin.DigitalItems.ProvisionInstallationEntitlementsBlock
  Plugin.DigitalItems.ProvisionDigitalProductEntitlementsBlock
  Plugin.DigitalItems.ProvisionWarrantyEntitlementsBlock

For orders to receive entitlements, both the AvailabilityAlwaysPolicy and a tag from their respective digital item type must be present, this includes the gift card entitlements, which in previous areas didn't validate against the AvailabilityAlwaysPolicy.

Once the Entitlement entity has been created it is then associated to the order as an Entity Reference.

If the order was placed by a registered customer, the entitlement will also be associated to the order as an Entity Reference.

Entitlement Rules - Standalone Sellable Item

Summary: Both the AvailabilityAlwaysPolicy and a digital item type tag will need to be associated to the sellable item to generate the respective entitlement and create an entity reference to it on the order.

Entitlement Rules - Sellable Items with Variants

Summary: The AvailabilityAlwaysPolicy must be present on the parent sellable item, while a digital item type tag will need to be associated to the variant to generate the respective entitlement and create an entity reference to it on the order.

* includes tags inherited from parent sellable item

Entitlement Rules - Customer Entitlements

Summary: Piggybacking off of the base entitlement rules above, if an entitlement has been created for an order and the customer was registered at the time the order was placed, the respective digital item type entitlement will be added to the Customer commerce entity.

Appendix

Terminology Definitions

  • Digital item tags: Tags configured in DigitalItemTagsPolicy.TagList.
  • Digital type tags: Any of the digital product tags, installation tags, or warranty tags.
  • Gift card tags: Tags configured in GiftCardTagsPolicy.TagList.
  • Digital product tags: Tags configured in KnownEntitlementsTags.DigitalProductTags.
  • Installation tags: Tags configured in KnownEntitlementsTags.InstallationTags.
  • Warranty tags: Tags configured in KnownEntitlementsTags.WarrantyTags.

Summary

We have covered how digital sellable items are treated throughout the Sitecore Commerce Engine. These are not limitations of the platform, as you are free to customise the functionality to your specific business requirements, but should be taken into consideration when evaluating business requirements and performing a gap analysis against platform functionality.

Sitecore Experience Commerce: Working with Digital Sellable Items – Part 1

In this article, we will review the difference between physical and digital sellable items, while focusing on the lesser documented digital sellable items. We will also review how to configure digital sellable items in the business manager.

For a more technical details behind the implementation of digital sellable items, see Working with Digital Sellable Items - Part 2.

Sellable Item Classifications

Sellable items can either represent physical or digital items. The following high-level overviews detail some of the discerning factors between the two classifications of sellable items.

Physical Sellable Items

Physical sellable items are any tangible product that a customer can purchase and have delivered. This is the default product type when creating sellable items via the Merchandising Manager in Sitecore Commerce.

Inventory

With physical items, inventory is required to track stock levels and prevent overselling.

Fulfillment Option Types

When purchasing physical items, during the checkout delivery step, a customer typically enters a delivery address of where the items will be delivered to, or selects a 'click and collect' style fulfillment option type where they can pick up the items from.

The SXA Storefront provides the common 'deliver to address' (physical) fulfillment option type.

Digital Sellable Items

Digital sellable items represent non-tangible products that a customer can purchase that entitle the customer to a form of product or service. Examples of digital sellable items include services, such as installation, warranties, subscriptions, digital downloads, online access, and digital currency (gift cards).

Inventory

As digital sellable items are non-tangible, they do not require inventory information to be associated to them. This can also be thought of as having perpetual inventory, therefore always being available for purchase.

Fulfillment Option Types

For digital sellable item purchases, the SXA Storefront provides a digital delivery sample implementation intended for digital gift card purchases, consisting of a recipient email and custom message, so the digital gift card can be personalised and delivered via email to the intended recipient.

Alternately, for other digital item fulfillment option types, custom implementations would be required to handle these scenarios, based on the client's requirements. Some ideas of requirements are as follows:

  • Services: A custom form, potentially with a third party integration, to create a booking system.
  • Warranties: A custom form to register the sellable item that the warranty was purchased for.
  • Subscriptions: A custom form to register the subscription's recipient details.
  • Digital Downloads: A custom form containing requesting the email address to send the digital download link to, or perhaps the user account functionality is customised to provide access to digital downloads.
  • Online access: Similar to digital downloads, the user may be given access to content via their logged in account, which may not require additional details to be taken during the Delivery step, however may require the user to make the purchase via a registered account.

Cart Lines

Digital sellable items are also separated into their own line items, e.g. when adding a digital sellable item with quantity of 2 this will create 2 cart lines with a quantity of 1, rather than 1 cart line with a quantity of 2. This provides the customer with the ability to input unique delivery information for each item during the checkout delivery step.

Entitlements

Another differentiating factor of digital sellable items are entitlements, which are registered to the order and customer account (where users are registered at the time of purchase), so that customer service representatives can view the current state of each entitlement.

Entitlements provides a basic implementation and a great starting point for customisation to meet business requirements.

Order Summary with entitlements
Customer Summary with entitements

Configuring Digital Sellable Items

Digital sellable items are determined by applying the appropriate Digital Sellable Item Tags on the sellable item and its variants. Let's review this together.

In the Business Tools,

  1. Navigate to the Merchandising Manager
  2. Locate an existing new sellable item or create a new sellable item and navigate to the Sellable Item page view
  3. Follow either Configuring Standalone Sellable Items or Configuring Sellable Item With Variants
  4. Ensure the sellable item has a valid list price and/or price card with an active price snapshot, so that the sellable item is purchasable
  5. Publish the sellable item

Configuring Standalone Sellable Items

  1. Add the appropriate tag that represents a Digital Item.
  2. Add the appropriate tag that represents the desired type of Digital Item Type.
    This will likely be the same tag as the Digital Item, therefore not required, however it is good to validate that the tags match, especially if custom tags have been assigned to the digital item and digital item type tag policies.
  3. Add the tag "entitlement".
Sellable item tags

Configuring Sellable Items With Variants

  1. On the sellable item, add the appropriate tag that represents a Digital Item.
  2. On each variant:
    1. Add the appropriate tag that represents the desired type of Digital Item Type.
    2. Add the tag "entitlement".

Note: Technically, the variants inherit tags from the parent sellable item only if no tags have been specified on the variant. Configuring any tags on a variant will remove the inherited tags from the variant, therefore these instructions specify the fool-proof solution.

Sellable item tags
Variant tags

Digital Sellable Item Tags

The following tables contain the default tags that are used to classify sellable items as digital and their digital item types.

Note: Tags for digital sellable items are not treated as case-sensitive.

Digital Item Tags

ClassificationDefault Tags
Digital Itementitlement
service
installation
subscription
digitalsubscription
warranty
onlinetraining
onlinelearning
giftcards

Digital Item Type Tags

TypeDefault Tags
Virtual Gift Cardgiftcards
Digital ProductOnlineTraining
OnlineLearning
Subscription
DigitalSubscription
Warranty Warranty
InstallationInstallation
Service

Working with the BizFx SDK: Preparing the Base Solution for Customisation

In this article, we will look at preparing the BizFx project for customisation, by first aligning the default configuration of the SDK with the configuration that was deployed with the Sitecore Commerce installation, and then reviewing how to build and deploy solution.

Fair warning: I am not an expert in Angular, however the information provided is enough for getting started and performing the bare minimum to align the BizFx SDK for custom solutions.

Creating the BizFx Development Solution

The Sitecore Commerce On Premise package contains the BizFx SDK and the speak files that will be required for the new project.

Extract the contents of the Sitecore.BizFX.SDK.*.*.*.zip into your desired folder location, e.g. C:\projects\, and copy the speak-ng-bcl-*.*.*.tgz and speak-styling-*.*.*-r*****.tgz files into the same folder as the SDK.

The BizFx SDK does come with a README.md file containing some general instructions on preparing the solution for building, however we will highlight the main aspects of these instructions and cover some addition steps for local and production deployments.

In src\assets\config.json we need to copy the values from our local BizFx installation, located by default at <web root>\<BizFx>\assets\config.json, so that when we deploy our new version the configuration isn't corrupted. You'll notice the values that need to be updated are named 'PlaceholderFor<context>'.

{
  "EnvironmentName": "HabitatAuthoring",
  "EngineUri": "PlaceholderForAuthoringUrl",
  "IdentityServerUri": "PlaceholderForIdentityServerUrl",
  "BizFxUri": "PlaceholderForBizFxUrl",
  "Language": "PlaceholderForDefaultLanguage",
  "Currency": "PlaceholderForDefaultCurrency",
  "ShopName": "PlaceholderForDefaultShopName",
  "LanguageCookieName": "selectedLanguage",
  "EnvironmentCookieName": "selectedEnvironment",
  "AutoCompleteTimeout_ms": 300
}

The other value that will need to be updated for projects will be the EnvironmentName, which is used to select the default environment in BizFx.

It is recommended that the LanguageCookieName and EnvironmentCookieName properties remain as their default value as they may only need to be changed for advanced customisations. We will not cover modifying these properties in this article.

Prerequisites for Building

Assuming node installed already, from the BizFx solution folder, open your preferred CLI tool and run the following commands:-

npm config set @speak:registry=https://sitecore.myget.org/F/sc-npm-packages/npm/
npm config set @sitecore:registry=https://sitecore.myget.org/F/sc-npm-packages/npm/
​​​​​​​npm install speak-ng-bcl-0.8.0.tgz
npm install speak-styling-0.9.0-r00078.tgz
npm install @sitecore/bizfx
npm install

Building and Deploying the BizFx Solution

For building the BizFx Angular application, the ng build command will compile into an output folder named dist, defaulting to the workspace folder. Within the dist folder, the sdk will be the equivalent of the <BizFx> website folder in the web root.

For production builds execute the ng build --prod command, which optimises the compiled solution for production deployments.

For more information about the Angular commands see https://angular.io/cli.

To deploy the BizFx solution, copy the contents of the dist/sdk into the <web root>\<BizFx> folder.

Building and Deploying via Gulp

For building and deploying the BizFx solution, I use a gulp script to wrap the angular commands. See the Source Code link at the end of the article to download the script.

If you haven't installed gulp, run the following command:-

​​​​​​​npm install gulp

Running the default gulp command will build the solution, clean out the BizFx folder in the web root and the deploy the solution to the BizFx folder.

As the gulp tasks will be performing operations on system restricted folders, make sure you run the gulp command under Administrator privileges.

Source Code: Ajsuth.BizFx.DeploymentScripts

Sitecore Experience Commerce: Managing Catalog Images

In this article, we will look at how to manage catalog images that are associated to sellable items in the Business Tools' Merchandising Manager.

With the Habitat Master catalog and SXA Storefront site that is setup during the installation of Sitecore Experience Commerce, we have a great reference point for implementing custom sites. However, one of the not so obvious configurations is how to configure catalog images for new catalogs.

Commerce Media Items OData API Key

In Sitecore's Core database (pre XC 9.1) or Master database (from XC 9.1), the Commerce Media Items OData API Key contains the Search Filter property, which is responsible for applying the filter of media items that are restricted in the search results.

(Fields/any(f: (f/Name eq 'Extension' and (f/Value eq 'png' or f/Value eq 'jpg'))) and Language eq 'en' and (contains(Path, '/images/adventure') or contains(Path, '/images/habitat')))

The Search Filter value itself is performing 3 levels of filtering:-

  1. Extension Type: The supported media extension types.
  2. Language: The language is utilised to prevent duplicate media items from being returned, i.e. per language version.
  3. Location: The media library folder where the catalog images are stored.

Note: This is a global configuration that will apply to all media search controls in Sitecore Commerce. This means that although we can segregate catalog images to different Media Library folders in Sitecore, the Image search results in the Business Tools will return results for all configured catalog image folder locations and extension types.  This makes sense considering sellable items can be associated across catalogs.

Configuring Image Extension Types

The default configured image types include jpg and png formats. To modify the extension types, we look at the extension part of the filter where we can add or remove the Value comparisons as desired.

(f: (f/Name eq 'Extension' and (f/Value eq 'png' or f/Value eq 'jpg')))

Configure the Value comparison in the format f/Value eq '<extension type>'. Don't modify the wrapping logic. When specifying multiple extensions use the 'or' clause as per the initial configuration.

Adding Extension Type Example

In this example, I will add the gif extension type and sample image to validate the change.

Configure the Extension Type

In the Sitecore Content Editor using the Core database (pre XC 9.1) or Master database (from XC 9.1) :-

  1. Go to /sitecore/system/Settings/Services/API Keys/CommerceMediaItemsODataAPIKey
  2. Update the extension part of the Search Filter value to include the 'gif' comparison.
(f: (f/Name eq 'Extension' and (f/Value eq 'png' or f/Value eq 'jpg' or f/Value eq 'gif')))

Add Images of the New Extension Type

In the Sitecore Content Editor using the Master database:-

  1. Go to /sitecore/media library/Images/<catalog image folder>
  2. Upload the media file to the folder.

    e.g. sunglasses.gif
  3. Publish the item.
  4. Re-index the master/web databases.

Add the Image to a Sellable Item

In the Business Tools' Merchandising Manager:-

  1. Navigation to the desired Sellable Item or create a new Sellable Item.
    1. Ensure the Sellable Item's Entity Version has not been published, otherwise a new version will need to be created.
  2. Under the Images section, click the Add an Image button.
  3. Search for the image name.
  4. Select the image name and click the tick (accept) button.
  5. Promote the sellable item through the workflow states until it's published.

View the Image on the Sellable Item in the Storefront

In the Storefront's website:-

  1. Either navigate to the Category or Search Results Page, containing the product, navigate to any page that contains any component that displays the Sellable Item, e.g. Promoted Products component, or navigate directly to the Product Details Page.

Configuring Image Source Location

For media image location, we need to configure the location part of the filter where we can add or remove the Path comparisons as desired.

By default, we see that the locations have been configured for the folders that have been utilised for the Adventure Works and Habitat catalogs.

(contains(Path, '/images/adventure') or contains(Path, '/images/habitat'))

Configure the Path to the location of the catalog images folder in the format contains(Path, '/<catalog image folder location>')  where <catalog image folder location> is under the Media Library Sitecore item tree. When using multiple folder locations (usually one folder per catalog), use the 'or' clause as per the initial configuration.

Update: I found that the contains function had some issues with parsing the Path value where spaces are used in the item names, hence '/images/adventure' rather than '/images/adventure works'. Another issue I found was that using the contains function also meant that any folder structure, outside the Media Library item, in the Sitecore tree that matched the path would also be included for the media search results causing undesired entries.

To resolve this, I found a more performant an accurate solution using the startswith function, which appears to resolve the Path value relative to the Media Library item and accepts spaces in the item names.

(startswith(Path, '/images/adventure works/')

Please note that I haven't updated the remainder of this article with this change, but keep it in mind when making updating the filter yourself.

Adding Catalog Image Folder Example

In this example, I will add the HelloWorld catalog image folder type and sample image to validate the change.

Configure the Location Folders

In the Sitecore Content Editor using the Core database (pre XC 9.1) or Master database (from XC 9.1) :-

  1. Go to /sitecore/system/Settings/Services/API Keys/CommerceMediaItemsODataAPIKey
  2. Update the location part of the Search Filter value to include the 'helloworld' comparison.
(contains(Path, '/images/adventure') or contains(Path, '/images/habitat') or contains(Path, '/images/helloworld'))

Add Images of the Catalog Image Folder

In the Sitecore Content Editor using the Master database:-

  1. Go to /sitecore/media library/Images/HelloWorld
  2. Upload the media file to the folder.

    e.g. sunglasses.jpg
  3. Publish the item.
  4. Re-index the master/web databases.

Search for the Image in the Merchandising Manager

In the Business Tools' Merchandising Manager:-

  1. Navigation to the desired Sellable Item or create a new Sellable Item.
    1. Ensure the Sellable Item's Entity Version has not been published, otherwise a new version will need to be created.
  2. Under the Images section, click the Add an Image button.
  3. Search for the image name.
  4. Ensure the image shows in the search results

Summary

We learnt that the configurations of image folder location and filtered media extension types for catalog images are set in the Search Filter field of the Commerce Media Items OData API Key Sitecore item in the Core database (pre XC 9.1) or Master database (from XC 9.1) . We also learnt that the configurations are global and will apply to all media search controls in the Business Tools.