Promotion Extensibility Developer Guide

TABLE OF CONTENTS

1. Introduction

1.1 Purpose

The Promotion Extensibility Framework enables developers to build custom promotion logic outside the core Znode application. Instead of modifying the product source code, custom promotions are implemented as independent extension libraries that are dynamically loaded and executed by the Znode Extension Framework.

This approach allows organizations to implement complex, business-specific promotion scenarios while remaining compatible with future Znode upgrades. Typical use cases include:

  • Custom order discounts
  • Customer-specific promotions
  • Industry-specific pricing rules
  • Loyalty-based discounts
  • Brand-specific offers
  • Membership pricing
  • Complex coupon validation
  • External system integrations

1.2 Intended Audience

This guide is intended for:

  • Solution Architects
  • Backend Developers
  • Technical Consultants
  • Znode Partners
  • Extension Developers
  • System Integrators

Readers should have working experience with:

  • C#
  • .NET 8
  • Dependency Injection
  • Object-Oriented Programming
  • Znode Administration

1.3 Scope

This guide explains how to:

  • Understand the Extension Framework and how it relates to Promotion Extensibility
  • Create, build, and deploy custom promotion extensions
  • Deploy extensions using the Znode CLI
  • Register promotion types and configure promotions in Admin
  • Use the Promotions SDK to access runtime context and helper APIs
  • Execute, debug, and monitor custom promotion logic

1.4 Benefits of Promotion Extensibility

Using the Promotion Extensibility Framework provides several advantages:

  • No modifications to Znode core source code
  • Upgrade-safe customizations
  • Independent deployment of promotion logic
  • Clear separation between product and customer-specific code
  • Dynamic extension loading with no application restart
  • Centralized logging and observability
  • Support for multiple custom promotion types running side by side
  • Simplified maintenance and versioning

2. Extension Framework Overview

2.1 What Is the Extension Framework?

The Znode Extension Framework is a runtime extensibility platform that enables developers to introduce new business capabilities without changing the core application. Instead of embedding custom code within the Znode source, functionality is packaged into extension assemblies (DLLs). These assemblies are deployed independently and loaded dynamically by the Extension Loader during application startup or synchronization.

This architecture enables organizations to customize business behavior while keeping the product maintainable and upgrade-friendly.

2.2 Why an Extension Framework?

Traditional customization approaches require direct modifications to the product source code, making upgrades costly and difficult. The Extension Framework addresses these challenges by providing:

  • Loose coupling between the core platform and custom implementations
  • Independent development and deployment of custom functionality
  • Version isolation for extension assemblies
  • Dynamic discovery of extensions
  • Standardized contracts through the SDK
  • Built-in observability and diagnostics

2.3 End-to-End Lifecycle

The following diagram illustrates the complete lifecycle of a promotion extension, from initial development through cart execution and logging.

Developer
    |
    v
Create Promotion Project
    |
    v
Reference Promotions SDK
    |
    v
Implement Promotion Logic
    |
    v
Build Extension DLL
    |
    v
Deploy using Znode CLI
    |
    v
Extension Storage
    |
    v
Extension Loader
    |
    v
Sync Extensions
    |
    v
Register Promotion Type
    |
    v
Create Promotion in Admin
    |
    v
Customer Cart
    |
    v
Promotion Engine
    |
    v
Execute Custom Promotion
    |
    v
Return Discount Result
    |
    v
Execution Logs

This lifecycle is explored step by step in Chapter 5 (development), Chapter 9 (deployment), Chapter 10 (registration and configuration), and Chapter 11 (runtime execution).

2.4 High-Level Components

Promotion SDK

Provides the contracts, interfaces, base classes, helper methods, and attributes required to develop custom promotions. Covered in detail in Chapter 6.

Extension Loader

Responsible for discovering, loading, and managing deployed promotion extensions. During synchronization, it reloads the latest extension assemblies without requiring changes to the core application.

Extension Storage

Stores the compiled extension assemblies that have been deployed using the Znode CLI. The Extension Loader retrieves these assemblies during synchronization.

Promotion Engine

Evaluates active promotions during cart calculation. It invokes the appropriate custom promotion classes and applies the discounts returned by the extension. See Chapter 11 for the full runtime execution pipeline.

Extension Observability

Provides visibility into extension execution by exposing deployment status, synchronization controls, and execution logs. This enables developers to monitor and troubleshoot custom promotions effectively. See Chapter 8.

2.5 Benefits of the Extension Framework

  • Upgrade-safe customizations
  • Dynamic extension loading
  • Independent deployment
  • Standardized SDK contracts
  • Centralized logging
  • Improved maintainability and better separation of concerns
  • Enhanced debugging and observability
  • Reduced product maintenance effort

3. Promotion Extensibility Overview

3.1 Overview

Promotion Extensibility enables developers to create custom promotion types that execute alongside Znode's built-in promotion engine without modifying the core product. Custom promotions are implemented as extension libraries using the Promotions SDK, deployed independently, and dynamically loaded through the Znode Extension Framework.

Unlike traditional customizations that require changes to the product source code, Promotion Extensibility provides a clean separation between the Znode platform and customer-specific business logic. This ensures that custom implementations remain upgrade-safe while allowing organizations to implement complex promotional requirements.

The Promotion Engine treats custom promotions the same way it treats built-in promotion types. Once a custom promotion is registered in Admin, business users can configure and manage it through the standard Promotion Management screens.

3.2 Supported Promotion Types

The framework supports extending all major promotion scenarios, including:

Promotion TypeDescription
Cart PromotionsDiscounts applied to the overall cart value
Product PromotionsDiscounts on selected products
Category PromotionsDiscounts for products within specific categories
Brand PromotionsBrand-based promotional offers
Shipping PromotionsShipping fee discounts
Coupon PromotionsCoupon-based promotions requiring customer input
Membership PromotionsCustomer segment

4. Prerequisites

Before developing custom promotions, ensure the following prerequisites are met.

4.1 Development Environment

ComponentRecommended Version
Visual Studio2022 Enterprise or Professional
.NET SDK.NET 8
Znode CLILatest version
GitLatest version
SQL ServerSQL Server 2022 or later

4.2 Required Knowledge

Developers should have working knowledge of:

  • C#
  • .NET 8
  • Dependency Injection
  • Object-Oriented Programming
  • Reflection and attributes
  • Znode Promotion Management
  • Basic commerce concepts

4.3 Required Access

Ensure you have access to:

  • Znode Admin
  • Custom API solution
  • Promotions SDK package
  • Znode CLI
  • Extension deployment permissions
  • Extension Observability page

4.4 Required SDK

Every custom promotion project must reference the Promotions SDK:


Znode10.Libraries.Promotions.SDK

This SDK provides:

  • CartPromotionTypeBase
  • PromotionExtensionAttribute
  • IPromotionContext
  • IPromotionCart
  • IPromotionBag
  • IPromotionCalculationHelper
  • PromotionCalculateResult
  • PromotionDiscountDetail
  • IExtensionLogger

Important: Without this SDK, custom promotion development is not possible.

Custom API Solution
│
├── Engine.Custom.Api
│
├── Znode.Libraries.Promotions.Custom
│   ├── FlatTenDollarPromotion.cs
│   ├── BrandPromotion.cs
│   ├── LoyaltyPromotion.cs
│
├── Shared
│
└── Tests

Organizing promotion extensions into a dedicated class library keeps business logic isolated, simplifies maintenance, and allows independent deployment.

5. Getting Started: Building Your First Custom Promotion

This chapter walks through the complete, end-to-end process of creating a custom promotion extension — from project creation through to a working, deployable discount. It is the single authoritative walkthrough for this workflow; later chapters build on the concepts introduced here rather than repeating them.

Development Workflow

Create Extension Project
     |
     v
Reference Promotions SDK
     |
     v
Create Promotion Class
     |
     v
Implement Business Logic
     |
     v
Build Project
     |
     v
Deploy Extension
     |
     v
Sync Extension
     |
     v
Register Promotion Type
     |
     v
Create Promotion
     |
     v
Execute & Validate

Step 1 — Create a New Promotion Project

A custom promotion should always be implemented in a separate Class Library rather than modifying any Znode core project. Keeping promotion logic isolated improves maintainability and ensures compatibility with future product upgrades.

Recommended naming conventions for the project:

  • Znode.Libraries.Promotions.Custom
  • Znode.Libraries.Promotions.Extensions
  • CompanyName.CustomPromotions
  • Contoso.PromotionExtensions

Recommendation: Avoid placing custom promotion logic directly inside the API project.

Step 2 — Reference the Promotions SDK

Add the following SDK package to the project:

Znode10.Libraries.Promotions.SDK

The SDK contains all the contracts required by the Promotion Engine, including base promotion classes, extension attributes, context objects, discount models, helper methods, dependency injection support, and logging interfaces.

Important: Without this package, the Promotion Engine cannot recognize or execute custom promotions.

Step 3 — Create the Promotion Class

Create a new class inside your project. Each class represents a single promotion type. Example names:

  • FlatTenDollarPromotion.cs
  • BrandDiscountPromotion.cs
  • CustomerTierPromotion.cs

Step 4 — Decorate the Class with PromotionExtension

Every promotion class must be decorated with the PromotionExtension attribute. The Extension Framework scans all deployed assemblies during synchronization, and only classes decorated with this attribute are discovered and registered as promotion extensions.


using Znode.Libraries.Promotions.SDK;
using Znode.Libraries.Promotions.SDK.Attributes;

namespace Znode.Libraries.Promotions.Custom;

[PromotionExtension(typeof(CartPromotionTypeBase))]
public class FlatTenDollarPromotion : CartPromotionTypeBase
{
}

Important: Without this attribute, the Extension Loader will ignore the class during discovery.

Step 5 — Inherit from CartPromotionTypeBase

Every promotion must inherit from the base class supplied by the SDK. The base class provides access to the promotion context, cart information, helper methods, promotion configuration, dependency injection, and discount creation helpers.

Important: Developers should never implement promotions from scratch without inheriting from the base class.

Step 6 — Configure Promotion Metadata

Inside the constructor, configure the promotion metadata:


public FlatTenDollarPromotion()
{
    Name = "Flat $10 Discount";
    Description = "Applies a flat $10 discount.";
    ClassName = "FlatTenDollarPromotion";
}
PropertyPurpose
NameDisplay name of the promotion
DescriptionDescription shown to administrators
ClassNameUnique identifier used during execution

Important: The ClassName value must exactly match the Class Name configured in Admin → Dev Center → Promotion Settings. This comparison is case-sensitive.

Step 7 — Override the Calculate() Method

Every promotion must override the Calculate() method. This is the entry point that the Promotion Engine invokes whenever the promotion is evaluated, and it is the only location where business logic should be implemented.


public override PromotionCalculateResult Calculate(
    int? couponIndex,
    IReadOnlyList<IPromotionInfo> allPromotions)
{
    var result = new PromotionCalculateResult();

    // Business logic

    return result;
}

When this method is invoked, the SDK has already initialized the following objects — there is no need to load them manually:

Promotion Context
     |
     v
Current Cart
     |
     v
Promotion Configuration
     |
     v
Applied Coupons
     |
     v
Customer Information
     |
     v
Catalog Information
     |
     v
Promotion Helper
     |
     v
Dependency Injection
     |
     v
Logging Services

Step 8 — Validate Cart and Business Conditions

Before applying a discount, validate whether the promotion is applicable, and return an empty result if it is not:


if (GetCartQuantity() <= 0)
{
    return new PromotionCalculateResult();
}

Common validations performed at this stage include:

ValidationPurpose
Cart is emptySkip execution
Minimum order amountEnsure order value qualifies
Minimum quantityValidate purchased quantity
Product / brand / category eligibilityRestrict to configured items
Coupon validationValidate coupon availability
Customer profileRestrict to customer segments
Portal validationRestrict to specific stores
Promotion priorityPrevent conflicting promotions

Step 9 — Retrieve Promotion Configuration and Cart Data

The configuration entered by the administrator is available through the Promotion Bag, and the shopping cart is available through the Cart object:


var bag  = Context.PromotionBag;
var cart = Context.Cart;

var promotionName = bag.PromotionName;
decimal subtotal   = cart.SubTotal;
decimal quantity   = cart.GetTotalItemCount();

Both objects are described fully in Chapter 7. When a promotion needs to evaluate individual products, iterate the cart items:


foreach (var item in Context.Cart.CartItems)
{
    var sku      = item.Product.SKU;
    var quantity = item.Quantity;
    var price    = item.ExtendedPrice;
}

Typical use cases for iterating cart items include Buy-X-Get-Y offers, category or brand discounts, product bundles, and tiered pricing.

Step 10 — Apply the Discount

Use the SDK's discount helper methods rather than constructing discount objects manually.

Order-Level Discount


result.OrderLevelDiscount =
    CreatePromoDiscount(
        10,
        DiscountLevelCode.OrderLevel);

result.IsApplied = true;

Line-Item Discount


result.LineItemDiscounts.Add(
    itemIndex,
    CreatePromoDiscount(
        discountAmount,
        DiscountLevelCode.LineItemLevel));

Shipping Discount


result.ShippingLevelDiscount =
    CreatePromoDiscount(
        20,
        DiscountLevelCode.ShippingLevel);

result.IsApplied = true;

Important: The Promotion Engine only applies a discount when IsApplied is explicitly set to true.

Step 11 — Return the Result

Always return a valid PromotionCalculateResult. Never return null.


return result;

Step 12 — Build and Verify

Compile the project to generate the extension DLL:


dotnet build
bin
└── Release
    └── net8.0
        Znode.Libraries.Promotions.Custom.dll

Only the compiled DLL is deployed to the Extension Framework (see Chapter 9 for deployment steps).

Complete Working Example

The following example combines every step above into a single, minimal but complete promotion implementation. 

This example demonstrates a custom promotion named FlatTenDollarPromotion created using the Promotion SDK.

When the promotion runs, it checks whether the cart has items and then applies a flat $10 order-level discount.


using Znode.Libraries.Promotions.SDK;
using Znode.Libraries.Promotions.SDK.Attributes;

namespace YourCompany.CustomPromotions;

[PromotionExtension(typeof(CartPromotionTypeBase))]
public class FlatTenDollarPromotion : CartPromotionTypeBase
{
    public FlatTenDollarPromotion()
    {
        Name = "Flat $10 Off";
        Description = "Applies a flat $10 discount.";
        ClassName = "FlatTenDollarPromotion";
    }

    public override PromotionCalculateResult Calculate(
        int? couponIndex,
        IReadOnlyList<IPromotionInfo> allPromotions)
    {
        var result = new PromotionCalculateResult();

        if (GetCartQuantity() <= 0)
            return result;

        result.OrderLevelDiscount = CreatePromoDiscount(
            10,
            DiscountLevelCode.OrderLevel);

        result.IsApplied = true;

        return result;
    }
}

Error Handling

A custom promotion should never terminate the Promotion Engine because of an unhandled exception. Wrap business logic in structured exception handling and log the failure:


try
{
    // Promotion logic
}
catch (Exception ex)
{
    var logger = ServiceProvider.GetService<IExtensionLogger>();

    await logger.LogErrorAsync(
        "Promotion",
        ClassName,
        nameof(Calculate),
        ex);

    return new PromotionCalculateResult();
}

Returning an empty result ensures that the Promotion Engine continues evaluating other promotions even if one extension fails.

Validation Checklist

Promotions SDK referenced
Separate class library created
ValidationStatus
Promotion inherits CartPromotionTypeBase
PromotionExtension attribute applied
Calculate() method implemented
ClassName configured and matches Admin
Project builds successfully

Common Mistakes

IssueImpactResolution
Missing PromotionExtension attributeExtension is never discoveredAdd the attribute to the class
Does not inherit CartPromotionTypeBasePromotion cannot executeInherit from the SDK base class
ClassName mismatchPromotion is never invokedEnsure Admin configuration matches exactly (case-sensitive)
Returning nullRuntime exceptionAlways return a new PromotionCalculateResult
Forgetting IsApplied = trueDiscount ignoredSet IsApplied only when a discount is successfully applied
Building the wrong projectIncorrect DLL deployedBuild only the extension project

6. Promotion SDK Overview

The Promotions SDK is the foundation of the Promotion Extensibility Framework. It provides a standardized programming model for developing custom promotions without requiring changes to the Znode core application, exposing the contracts, base classes, helper methods, and context objects required to implement promotion logic. Every custom promotion should use these SDK components rather than interacting directly with the internal promotion engine.

6.1 SDK Architecture

The following diagram illustrates how the Promotion SDK interacts with the Extension Framework and Promotion Engine.

Promotion Engine
(executes Calculate())
     |
     v
CartPromotionTypeBase
     |
     v
Context | Helper APIs | Logging | DI Services | Base Methods
     |
     v
Cart | Promotions | Coupons | Products | Customer | Portal

The SDK abstracts the internal implementation of the Promotion Engine and provides developers with only the APIs required to implement business logic.

6.2 SDK Components

ComponentPurpose
CartPromotionTypeBaseBase class for all cart promotions
PromotionExtension attributeRegisters the class as an extension
Promotion ContextProvides runtime data
Promotion BagProvides promotion configuration
Cart / Product / Coupon ObjectsRuntime shopping cart, product, and coupon information
Helper APIsCommon promotion calculations and lookups
LoggerExtension logging
Discount ModelsApply discounts to the cart

6.3 CartPromotionTypeBase

Every custom promotion must inherit from CartPromotionTypeBase. Without inheriting from this base class, the promotion cannot participate in the execution pipeline.


public class FlatDiscountPromotion : CartPromotionTypeBase
{
}

The base class exposes the following inherited properties:

PropertyDescription
ContextComplete promotion execution context (see Chapter 7)
ServiceProviderDependency Injection container
NamePromotion display name
DescriptionPromotion description
ClassNameUnique promotion identifier

6.4 Dependency Injection

The SDK supports Dependency Injection through the built-in ServiceProvider. Developers may resolve any registered service from the DI container, such as logging, configuration, external APIs, repository services, or cache providers.


var logger = ServiceProvider.GetService();

6.5 Base Class Helper Methods

The base class exposes several helper methods that eliminate repetitive code and ensure consistent discount creation:

MethodPurpose
GetCartSubTotal()Returns the cart subtotal before discounts
GetCartQuantity()Returns the total quantity across the cart
CreatePromoDiscount()Creates an order/line/shipping-level promotion discount
CreateCouponDiscount()Creates a coupon-based discount

The full set of helper APIs — including product, category, brand, and catalog lookups exposed through IPromotionCalculationHelper — is covered in Chapter 12.

6.6 PromotionCalculateResult

Every promotion returns a PromotionCalculateResult, which communicates the outcome of the promotion execution back to the Promotion Engine:


var result = new PromotionCalculateResult();

It may contain:

  • Order-level discount
  • Shipping-level discount
  • Line-item discounts
  • Coupon result
  • The IsApplied flag

Important: The engine applies discounts only when IsApplied is set to true.

6.7 SDK Best Practices

  • Always inherit from CartPromotionTypeBase.
  • Access runtime data through the Context object rather than external services.
  • Use SDK helper methods instead of custom implementations.
  • Resolve services through ServiceProvider rather than creating instances manually.
  • Use CreatePromoDiscount() and CreateCouponDiscount() to construct discount objects.
  • Keep business logic focused on promotion rules; avoid embedding unrelated application logic.
  • Return a valid PromotionCalculateResult in every execution path.

7. Understanding the Promotion Context

IPromotionContext is the central object provided by the Promotions SDK. It contains all the runtime information required to evaluate and apply a promotion during cart calculation. Every custom promotion receives an initialized Context object through CartPromotionTypeBase, and it should be used as the single entry point for accessing cart information, promotion configuration, helper methods, and other runtime data.

7.1 Context Architecture

IPromotionContext
     |
     v
PromotionCart | PromotionBag | Helper APIs | ServiceProvider
     |
     v
Cart Items, Coupons, Customer, Products /
Promotion Info, Coupons, Settings, Attributes /
Product, Category, Brand, Catalog Lookup
PropertyTypeDescription
CartIPromotionCartCurrent shopping cart
PromotionBagIPromotionBagPromotion configuration from Admin
HelperIPromotionCalculationHelperHelper methods for promotion validation
ServiceProviderIServiceProviderDependency Injection container

public override PromotionCalculateResult Calculate(
    int? couponIndex,
    IReadOnlyList allPromotions)
{
    var cart      = Context.Cart;
    var promotion = Context.PromotionBag;
    var helper    = Context.Helper;

    return new PromotionCalculateResult();
}

7.2 IPromotionCart

Represents the customer's shopping cart, providing access to cart totals, customer information, products, coupons, and order details.

OrderIdint?Existing order ID (if applicable)
UserIdint?Logged-in customer ID
ProfileIdint?Customer profile
PortalIdint?Current store identifier
SubTotaldecimalCart subtotal before discounts
PropertyTypeDescription
PropertyTypeDescription
IsAnyPromotionAppliedboolWhether another promotion has already been applied
CartItemsIReadOnlyList<IPromotionCartItem>Products in the cart
CouponsIReadOnlyList<ICouponInfo>Coupons entered by the customer

Useful Methods


decimal quantity =
    Context.Cart.GetTotalItemCount();

decimal maxDiscount =
    Context.Cart.GetMaximumApplicableDiscountOnLineItem(
        discount,
        quantity,
        product);

// ensures a discount never exceeds product price

int sequence =
    Context.Cart.GetDiscountAppliedSequence(discountCode);

7.3 IPromotionCartItem

Each product in the shopping cart is represented by an IPromotionCartItem.

PropertyTypeDescription
ProductIPromotionProductProduct details
QuantitydecimalOrdered quantity
PromotionalPricedecimalCurrent promotional price
ExtendedPricedecimalTotal line amount
ExtendedPriceDiscountdecimalDiscount applied to the line
ParentProductIdintParent product for grouped/configurable products
Custom1–Custom5stringCustom fields available for business use

foreach (var item in Context.Cart.CartItems)
{
    var sku       = item.Product.SKU;
    var quantity  = item.Quantity;
    var lineTotal = item.ExtendedPrice;
}

7.4 IPromotionProduct

PromotionalPricePromotional selling price
FinalPriceFinal configured price
BrandCodeBrand identifier
SKUProduct SKU
ProductIDInternal product identifier
PropertyDescription
PropertyDescription
DiscountAmountTotal discount applied
OrderDiscountAmountOrder-level allocated discount
SelectedQuantityQuantity selected
GroupProducts / ConfigurableProducts / AddonProductsChild, configurable, and add-on products

foreach (var item in Context.Cart.CartItems)
{
    if (item.Product.BrandCode == "APPLE")
    {
        // Brand-specific logic
    }
}

7.5 IPromotionBag

Contains all configuration entered by administrators when creating the promotion. Unlike the cart, this object represents promotion configuration, not runtime data.

PropertyDescription
PromotionIdPromotion record ID
PromotionNameDisplay name
PromotionMessageCustomer message
PromotionTypeIdPromotion type
PromoCodePromotion or coupon code
PortalId / ProfileIdPortal / customer profile restriction
IsUniqueUnique coupon flag
HasCouponsIndicates a coupon-based promotion
CouponsCoupons configured in Admin

var promotionName =
    Context.PromotionBag.PromotionName;

var promoCode =
    Context.PromotionBag.PromoCode;

7.6 IPromotionInfo

The allPromotions parameter passed to Calculate() contains every active promotion currently being evaluated.

DiscountDiscount value
NamePromotion name
PromoCodeCoupon code
PromotionIdPromotion identifier
PropertyDescription
PropertyDescription
OrderMinimum / QuantityMinimumMinimum order amount / quantity
IsCouponRequiredCoupon requirement
DisplayOrderPromotion priority
IsUniqueUnique coupon
ClassName / ClassTypePromotion class and type
AttributesUser-defined fields

Filter this list to the promotions relevant to the current class — this should typically be the first operation performed inside Calculate():


var promotions = Context.GetPromotionsByType(
    "Cart",
    ClassName,
    allPromotions,
    "PromotionId");

7.7 ICouponInfo

Coupons are available from two locations: customer-entered coupons on the cart, and coupons configured for the promotion.


Context.Cart.Coupons
Context.PromotionBag.Coupons
PropertyDescription
CodeCoupon code
InitialQuantity / AvailableQuantityIssued / remaining quantity
IsActiveCoupon status
CouponValidCoupon validation result
CouponAppliedApplied flag
CouponMessageCustomer message
IsExistInOrderAlready used in an order

foreach (var coupon in Context.PromotionBag.Coupons)
{
    if (coupon.AvailableQuantity > 0)
    {
        // Apply coupon
    }
}

7.8 IPromotionCalculationHelper

Provides reusable APIs for common promotion operations such as product, category, brand, and catalog lookups. The complete list of methods is detailed in Chapter 12.


var helper = Context.Helper;

var productIds =
    Context.Helper.GetPromotionProductIds(
        promotionId);

7.9 Best Practices

  • Always access runtime data through the Context object instead of external services.
  • Use Context.Helper methods for product, category, brand, and catalog lookups instead of writing custom queries.
  • Treat IPromotionCart as read-only; return changes through PromotionCalculateResult.
  • Avoid modifying cart or coupon objects directly.
  • Keep business logic focused on evaluating promotion eligibility and calculating discounts.

8. Logging and Observability

Since promotion execution occurs within the Promotion Engine rather than a traditional web request, developers need visibility into the execution flow, validation decisions, applied discounts, and runtime exceptions. The framework provides this through the IExtensionLogger interface and the Extension Observability page in Znode Admin.

Without proper logging, it can be difficult to determine whether a promotion was discovered, whether Calculate() was invoked, why a promotion was skipped or a discount was not applied, whether an exception occurred, or which promotion was applied to a given cart.

8.1 Logging Architecture

Promotion Engine
     |
     v
Custom Promotion Extension
     |
     v
IExtensionLogger
     |
     v
Extension Logging Service
     |
     v
Extension Observability Page
     |
     v
Execution Logs & Diagnostics

8.2 IExtensionLogger

Unlike writing directly to a file or console, this logger integrates with the Extension Framework and surfaces logs in the Extension Observability page. Resolve it using Dependency Injection:


var logger = ServiceProvider?.GetService();

Best Practice: Resolve the logger inside Calculate() rather than the constructor — the service provider may not be available during object construction.

8.3 Writing Logs

Informational Logs

Useful for tracing execution flow, successful application, and validation outcomes:


await logger.LogInformationAsync(
    "Znode.Libraries.Promotions.Custom",
    ClassName,
    nameof(Calculate),
    "Promotion execution started.");

Typical informational events: execution started, validation completed, promotion skipped (with the reason), discount applied, execution completed.

Warnings

Indicate unexpected but non-fatal conditions such as an expired coupon, incomplete promotion configuration, a missing attribute, an empty product list, or a zero discount value:


await logger.LogWarningAsync(
    "Znode.Libraries.Promotions.Custom",
    ClassName,
    nameof(Calculate),
    "Promotion configuration does not contain eligible products.");

Errors

Unexpected exceptions should always be logged:


try
{
    // Promotion logic
}
catch (Exception ex)
{
    await logger.LogErrorAsync(
        "Znode.Libraries.Promotions.Custom",
        ClassName,
        nameof(Calculate),
        ex);

    return new PromotionCalculateResult();
}
StageLog Level
Promotion execution startedInformation
Validation completedInformation
Promotion skippedInformation
Discount appliedInformation
Missing configurationWarning
Invalid couponWarning
Unexpected exceptionError
Promotion execution completedInformation

Example log sequence for a successful execution:

INFO  Promotion execution started.
INFO  Minimum order validation passed.
INFO  Eligible products found.
INFO  Applied $10 order discount.
INFO  Promotion execution completed successfully.

8.5 Extension Observability

The Extension Observability page provides centralized visibility into all deployed extensions, allowing developers and administrators to view deployed extensions, reload updated DLLs, monitor execution, and diagnose runtime failures.

Navigate to:

Admin → Extension Observability

The page provides two key capabilities:

  1. Extension Synchronization — synchronizes the latest deployed extension assemblies with the running application.
  2. Execution Logs — displays runtime logs generated by custom extensions.

Important: Always click Sync after deploying a new or updated extension. Otherwise, the Promotion Engine will continue executing the previously loaded version.

8.6 Common Troubleshooting Scenarios

IssuePossible CauseResolution
Promotion never executesMissing PromotionExtension attributeAdd the attribute and redeploy
Old code still executesExtension not synchronizedClick Sync in Extension Observability
No logs appearLogger not resolved or logging not implementedVerify IExtensionLogger usage
Promotion skippedValidation conditions not metReview informational logs
Runtime exceptionError in custom logicCheck Error logs and exception details

8.7 Logging Best Practices

  • Log meaningful business events rather than every line of code.
  • Include the promotion class name in every log entry.
  • Use consistent and descriptive log messages.
  • Avoid logging sensitive customer information.
  • Use Warning for recoverable issues and Error for unexpected exceptions.
  • Keep log messages concise and actionable.
  • Enable Information, Warning, and Error logging in production; avoid excessive debug logging.
  • Review execution logs after every extension deployment.

9. Deployment and Extension Management

Once a custom promotion has been developed and tested, it must be deployed to the Znode Extension Framework before the Promotion Engine can use it. Unlike traditional deployments that require redeploying the whole application, extensions are deployed independently and activated through the Extension Observability page.

9.1 Deployment Overview

Build Extension Project
     |
     v
Generate Extension DLL
     |
     v
Deploy using Znode CLI
     |
     v
Extension Storage
     |
     v
Extension Loader
     |
     v
Click Sync
     |
     v
Reload Latest DLL
     |
     v
Promotion Ready for Execution

Only the extension assembly is deployed — the core Znode application remains unchanged.

9.2 Deployment Prerequisites

RequirementDescription
Extension project builds successfullyNo compilation errors
Znode CLI installedLatest supported version
CLI authenticatedConnected using a valid token
Extension deployment permissionsRequired to upload extensions
Admin accessRequired for synchronization
Extension Observability accessRequired to reload extensions

9.3 Step 1 — Build the Extension


dotnet build

bin
└── Release
    └── net8.0
        Znode.Libraries.Promotions.Custom.dll

Only successfully compiled assemblies should be deployed.

9.4 Step 2 — Connect to the Znode CLI

Authenticate the CLI with the target environment before deploying:


znode connect <token>

znode connect eyJhbGciOiJIUzI1NiIs...

9.5 Step 3 — Deploy a Single Extension Project


znode deploy extension "<path-to-project.csproj>"

znode deploy extension "D:\Extensions\Znode.Libraries.Promotions.Custom\Znode.Libraries.Promotions.Custom.csproj"

During deployment, the CLI builds the project, packages the extension, uploads the compiled assembly, and registers the new extension version.

9.6 Step 4 — Deploy Multiple Extension Projects

To deploy several extension projects at once, point the CLI at the containing folder:


znode deploy extension "<path-to-folder>"

znode deploy extension "D:\Extensions"

The CLI automatically searches for supported extension projects, builds each one, and uploads all generated assemblies. This is the recommended approach when deploying multiple promotion extensions together.

9.7 Step 5 — Synchronize the Extension

After deployment, navigate to Admin → Extension Observability and click Sync. Synchronization detects newly deployed assemblies, reloads updated versions, refreshes the Extension Loader, and makes the latest extension available to the Promotion Engine.

Important: Deployment alone does not activate the extension. Synchronization is required before the Promotion Engine can execute the latest version.

9.8 Verifying Deployment

  • The extension appears in the Extension Observability page.
  • No loading errors are reported.
  • Execution logs are available.
  • The promotion type can be registered in Admin.
  • The Promotion Engine executes the new extension.

9.9 Updating an Existing Extension

  • Modify the source code.
  • Build the project.
  • Deploy the updated extension.
  • Synchronize the application.
  • Validate the updated behavior.

No changes are required to the core Znode application.

9.10 Extension Version Management

The Extension Framework supports independent versioning of custom extensions.

  • Version each release using semantic versioning (e.g., 1.0.0, 1.1.0, 2.0.0).
  • Maintain release notes for each deployment.
  • Avoid breaking changes in minor releases.
  • Test new versions in a non-production environment before deployment.
VersionDescription
1.0.0Initial implementation
1.1.0Added category validation
1.2.0Improved logging
2.0.0Introduced coupon enhancements

9.11 Rollback Strategy

  • Redeploy the previous stable version.
  • Synchronize the application.
  • Verify that the earlier version is active.
  • Investigate the issue before redeploying the updated version.

Maintaining previous builds in source control or an artifact repository simplifies rollback.

9.12 Common Deployment Issues

IssuePossible CauseResolution
CLI connection failsInvalid or expired tokenReconnect using a valid token
Build failsCompilation errorsResolve errors before deployment
Extension not visibleDeployment unsuccessfulVerify CLI output and upload status
Old logic still executesSynchronization not performedClick Sync in Extension Observability
Promotion not foundPromotion type not registeredRegister the class in Promotion Settings
Extension load errorMissing dependencies or incompatible assemblyVerify project references and rebuild

9.13 Deployment Checklist

ValidationStatus
Project builds successfully
CLI connected to target environment
Extension deployed successfully
Synchronization completed
Extension loaded without errors
Promotion type registered
Promotion created in Admin
Execution logs verified
Functional testing completed

9.14 Deployment Best Practices

  • Build in Release configuration for production deployments.
  • Test all promotion logic before deployment.
  • Deploy only the intended extension project.
  • Synchronize immediately after deployment.
  • Review execution logs to confirm successful loading.
  • Version extensions consistently and keep deployment artifacts in a secure repository.
  • Document deployment history and release notes.

10. Registering Promotion Types and Configuring Promotions

After deploying and synchronizing a custom promotion extension, the next step is to register the promotion type within Znode Admin. Registration allows the Promotion Engine to associate a configured promotion with the corresponding extension class. Once registered, business users can create and manage promotions through the standard Promotion Management interface without further code changes.

10.1 Registration Workflow

Deploy Extension DLL
     |
     v
Synchronize Extension
     |
     v
Register Promotion Type
     |
     v
Configure Promotion Attributes
     |
     v
Create Promotion
     |
     v
Activate Promotion
     |
     v
Customer Adds Products
     |
     v
Promotion Engine Executes
     |
     v
Discount Applied

10.2 Prerequisites

RequirementStatus
Extension successfully deployed
Extension synchronized
Promotion class discovered
Admin access available
Custom promotion DLL loaded

10.3 Step 1 — Open Promotion Settings

Navigate to:

Admin Portal → Dev Center → Promotion Settings → Promotion Types.

This page displays all available promotion types, including both built-in and custom extensions.

10.4 Step 2 — Create a New Promotion Type

Click Add Promotion Type and provide the required information:

FieldValue
NameFlat Order Discount
DescriptionApplies a flat discount on qualifying orders
Class NameFlatTenDollarPromotion
StatusActive

Class Name

The Class Name is the most critical configuration field. It must exactly match the value assigned in the promotion class constructor:


public FlatTenDollarPromotion()
{
    ClassName = "FlatTenDollarPromotion";
}

Important: The class name comparison is case-sensitive. Any mismatch prevents the Promotion Engine from locating the extension.

10.5 Step 3 — Configure User-Defined Fields (Optional)

Some promotions require configuration values that are not part of the standard promotion model — such as minimum purchase amount, eligible customer tier, brand code, category ID, maximum discount, reward points, or an external campaign identifier. These are captured using User-Defined Fields (UDFs):

Field NameData TypeExample Value
MinimumOrderDecimal500
DiscountPercentDecimal10
BrandCodeTextNIKE
MaxDiscountDecimal200
CustomerTierTextGold

These values become available through the PromotionBag during execution:


var minimumOrder =
    Context.PromotionBag.Attributes["MinimumOrder"];

var discount =
    Context.PromotionBag.Attributes["DiscountPercent"];

Using configurable attributes allows business users to change promotion behavior without requiring code changes or redeployment.

10.6 Step 4 — Create a Promotion

Navigate to:

Marketing → Promotions → Create Promotion

Then select the custom promotion type registered in the previous step.

ConfigurationDescription
Promotion NameDisplay name
Promotion TypeCustom promotion type
Start Date / End DateActivation and expiration dates
PriorityExecution priority
ActiveEnable/disable promotion
StoresApplicable portals
Customer ProfilesTarget customer groups
User-Defined FieldsCustom configuration

10.7 Coupon Configuration

If the promotion requires a coupon, enable coupon support during promotion creation:

PropertyDescription
Coupon CodeCustomer-entered code
Expiration DateCoupon validity
Available QuantityNumber of uses
Maximum UsageUsage limit
Unique CouponOne-time use

Coupon Code: WELCOME10

During execution:


foreach (var coupon in Context.Cart.Coupons)
{
    if (coupon.Code == "WELCOME10")
    {
        // Apply promotion
    }
}

10.9 Verifying Registration

  • The promotion type appears in the Promotion Settings page.
  • The custom class name matches the implementation.
  • The promotion is active and appears in the Promotions list.
  • The extension executes during cart calculation.
  • Execution logs are visible in Extension Observability.

10.10 Common Configuration Issues

IssueCauseResolution
Promotion type not visibleExtension not synchronizedSynchronize the extension
Promotion never executesIncorrect Class NameEnsure Admin configuration matches the class exactly
Promotion unavailablePromotion inactiveActivate the promotion
Coupon ignoredCoupon not configuredVerify coupon settings and validity
User-defined fields emptyAttribute names mismatchEnsure attribute keys match between Admin and code
Promotion not appliedBusiness rules not satisfiedReview validation logic and execution logs

10.11 Example Configuration

SettingValue
Promotion NameSummer Sale
Promotion TypeFlat Order Discount
Class NameFlatTenDollarPromotion
Discount10 USD
Minimum Order100 USD
Start Date01-Jun-2026
End Date30-Jun-2026
Coupon RequiredNo
ActiveYes

10.12 Best Practices

  • Use descriptive names for promotion types and promotions.
  • Keep the ClassName consistent across code and Admin.
  • Prefer configurable user-defined fields over hardcoded values.
  • Validate promotion settings before activating them.
  • Test each promotion in a non-production environment.
  • Document the purpose and configuration of each custom promotion for administrators.

11. Promotion Execution Flow and Runtime Processing

This chapter explains the complete runtime lifecycle of a promotion — from the moment a customer performs an action in the storefront until the promotion result is applied to the shopping cart. Understanding this pipeline helps developers optimize promotion logic, troubleshoot issues effectively, and avoid common implementation mistakes.

11.1 When Is the Promotion Engine Invoked?

The engine evaluates all eligible promotions — built-in and custom, using the same execution pipeline — whenever one of the following customer actions occurs:

Customer ActionPromotion Evaluation
Add product to cart
Remove product
Update quantity
Apply / remove coupon
View shopping cart
Checkout
Shipping method change
Cart recalculation

Developers do not need to manually invoke the Promotion Engine.

11.2 Runtime Execution Pipeline

Step 1 — Load Active Promotions

The engine retrieves promotions that satisfy active status, current date/time, portal, customer profile, and store assignment. Inactive or expired promotions are ignored.

Step 2 — Validate Promotion Eligibility

The engine validates promotion dates, portal and customer restrictions, coupon requirements, promotion status, and execution priority. Only eligible promotions continue to the next stage.

Step 3 — Discover Extension Class

The Extension Framework locates the registered promotion class using the configured Class Name and creates an instance. If the class cannot be found, the promotion is skipped and an error is logged.

Promotion Type
     |
     v
FlatTenDollarPromotion (Class Name)
     |
     v
Extension Loader
     |
     v
FlatTenDollarPromotion.dll
     |
     v
Create Instance

Step 4 — Create Promotion Context

Before invoking the promotion, the SDK initializes a fully populated runtime context — cart, cart items, products, coupons, promotion configuration, helper APIs, service provider, and logger.

Step 5 — Execute Calculate()

The engine invokes the overridden Calculate() method. This is the only location where promotion business logic should be implemented.

Step 6 — Business Rule Evaluation

Inside Calculate(), the promotion evaluates conditions such as minimum order amount, product/brand/category eligibility, customer profile, coupon validity, and quantity. If conditions are not satisfied, an empty result is returned:


if (Context.Cart.SubTotal < 500)
{
    return new PromotionCalculateResult();
}

Step 7 — Discount Calculation

Once eligibility is confirmed, the applicable discount is calculated using the SDK's discount helpers:


var result = new PromotionCalculateResult();

result.OrderLevelDiscount =
    CreatePromoDiscount(
        100,
        DiscountLevelCode.OrderLevel);

result.IsApplied = true;

Step 8 — Return Result

The completed PromotionCalculateResult is returned to the Promotion Engine, which validates and applies the discount:


return result;

11.3 Execution Sequence

Customer
     |
     v
Shopping Cart
     |
     v
Promotion Engine
     |
     v
Extension Loader
     |
     v
Custom Promotion
     |
     v
Calculate()
     |
     v
Promotion Result
     |
     v
Promotion Engine
     |
     v
Updated Cart
     |
     v
Customer

11.6 Execution Flow Example

Promotion: "Flat $100 Off on Orders Above $1000"

ProductPrice
Laptop$900
Mouse$150
Keyboard$100

Subtotal: $1,150

  • Customer opens the cart.
  • Promotion Engine starts evaluation.
  • Active promotions are loaded.
  • FlatOrderPromotion is identified.
  • Context is created.
  • Calculate() executes.
  • Minimum order validation passes.
  • $100 discount is generated.
  • Promotion result is returned.
  • Cart total becomes $1,050.
  • Execution is logged.

11.7 Performance Considerations

Promotion execution occurs frequently during the customer journey, so inefficient logic can negatively impact cart and checkout performance.

  • Exit early when validation fails.
  • Avoid unnecessary loops over cart items.
  • Use SDK helper methods for lookups.
  • Cache repeated calculations within Calculate().
  • Avoid database or external API calls whenever possible.
  • Resolve services once per execution.

11.8 Runtime Error Handling

A failure in one custom promotion should not prevent other promotions from executing. Always wrap complex logic in exception handling and return an empty result on failure, so the Promotion Engine continues evaluating other promotions:


try
{
    // Promotion logic
}
catch (Exception ex)
{
    await logger.LogErrorAsync(
        "Promotion",
        ClassName,
        nameof(Calculate),
        ex);

    return new PromotionCalculateResult();
}

12. SDK Helper Methods and Discount APIs

The Promotions SDK provides a rich set of helper methods that simplify common promotion development tasks. Instead of implementing complex validation or discount logic manually, developers should leverage these built-in APIs to ensure consistency, improve performance, and reduce maintenance effort. This chapter is the complete reference for the helper method categories introduced in Chapters 6 and 7.

12.1 Helper Method Categories

CategoryPurpose
Cart HelpersRetrieve cart totals, quantities, and validation data
Product HelpersAccess eligible products
Category HelpersRetrieve promotion categories
Brand HelpersValidate product brands
Catalog HelpersAccess catalog mappings
Coupon HelpersValidate coupon information
Discount HelpersCreate order, line-item, and shipping discounts

12.2 Cart Helper Methods

GetCartSubTotal()

Returns the subtotal of the shopping cart before discounts. Typical use cases: minimum order amount validation, tiered pricing, free shipping eligibility.


if (GetCartSubTotal() >= 500)
{
    // Apply promotion
}

GetCartQuantity()

Returns the total quantity of all items in the cart.


if (GetCartQuantity() >= 5)
{
    // Apply bulk purchase discount
}

Accessing Cart Items


foreach (var item in Context.Cart.CartItems)
{
    // Process each item
}

12.3 Product, Category, Brand, and Catalog Helpers

GetPromotionProductIds()


var productIds =
    Context.Helper.GetPromotionProductIds(promotionId);

if (productIds.Contains(item.Product.ProductID))
{
    // Eligible product
}

GetPromotionCategoryIds() / GetCategoryIdsByProduct()


var categoryIds =
    Context.Helper.GetPromotionCategoryIds(promotionId);

var categories =
    Context.Helper.GetCategoryIdsByProduct(item.Product.ProductID);

if (categories.Any(c => categoryIds.Contains(c)))
{
    // Product belongs to an eligible category
}

GetPromotionBrandCodes()


var brands =
    Context.Helper.GetPromotionBrandCodes(promotionId);

if (brands.Contains(item.Product.BrandCode))
{
    // Brand qualifies
}

GetPromotionCatalogIds()

Returns all catalogs configured for the promotion — useful when promotions are restricted to specific catalogs.


var catalogs =
    Context.Helper.GetPromotionCatalogIds(promotionId);

12.4 Coupon Helper Methods

Customer-entered coupons are available through the cart:


var coupons = Context.Cart.Coupons;

foreach (var coupon in coupons)
{
    if (coupon.Code == "WELCOME10")
    {
        // Coupon is present
    }
}

12.5 Discount Helper Methods

Always use these SDK methods instead of creating discount objects manually.

CreatePromoDiscount()


result.OrderLevelDiscount =
    CreatePromoDiscount(
        100,
        DiscountLevelCode.OrderLevel);

result.IsApplied = true;

CreateCouponDiscount()


result.OrderLevelDiscount =
    CreateCouponDiscount(
        coupon.Code,
        100,
        DiscountLevelCode.OrderLevel);

Both methods automatically populate required metadata such as promotion name, promotion type, discount code, and promotion message.

12.6 Discount Levels

Discount LevelDescription
OrderLevelDiscount applied to the entire order
LineItemLevelDiscount applied to individual products
ShippingLevelDiscount applied to shipping charges

// Order-level
result.OrderLevelDiscount =
    CreatePromoDiscount(
        50,
        DiscountLevelCode.OrderLevel);

// Line-item
result.LineItemDiscounts.Add(
    lineItemIndex,
    CreatePromoDiscount(
        20,
        DiscountLevelCode.LineItemLevel));

// Shipping
result.ShippingLevelDiscount =
    CreatePromoDiscount(
        shippingCharge,
        DiscountLevelCode.ShippingLevel);

12.7 Maximum Discount Validation

Always ensure discounts do not exceed the value of the eligible product or order:


decimal maxDiscount =
    Context.Cart.GetMaximumApplicableDiscountOnLineItem(
        requestedDiscount,
        item.Quantity,
        item.Product);

Using this helper prevents negative prices and over-discounting.

12.8 Retrieving Applicable Promotions

The Calculate() method receives all active promotions. Filter the list to the promotions applicable to the current extension to reduce unnecessary processing:


var promotions =
    Context.GetPromotionsByType(
        "Cart",
        ClassName,
        allPromotions,
        "PromotionId");

12.9 Helper Method Quick Reference

Retrieve promotion productsGetPromotionProductIds()
Validate minimum quantityGetCartQuantity()
Validate minimum orderGetCartSubTotal()
RequirementRecommended Helper
RequirementRecommended Helper
Retrieve categoriesGetPromotionCategoryIds()
Retrieve brandsGetPromotionBrandCodes()
Retrieve catalogsGetPromotionCatalogIds()
Create order discountCreatePromoDiscount()
Create coupon discountCreateCouponDiscount()
Validate maximum discountGetMaximumApplicableDiscountOnLineItem()

12.10 Combined Example

The following example combines several helper methods in a single promotion:


public override PromotionCalculateResult Calculate(
    int? couponIndex,
    IReadOnlyList allPromotions)
{
    var result = new PromotionCalculateResult();

    if (GetCartSubTotal() < 1000) return result;
    if (GetCartQuantity() < 2) return result;

    result.OrderLevelDiscount = CreatePromoDiscount(
        100,
        DiscountLevelCode.OrderLevel);

    result.IsApplied = true;

    return result;
}

13. Best Practices and Troubleshooting

This chapter consolidates every recommendation introduced throughout this guide into a single reference, along with the complete troubleshooting reference for diagnosing issues in custom promotion extensions.

13.1 Development Best Practices

Keep promotions focused

Implement a single business rule per promotion class. Avoid combining multiple unrelated conditions in one class.

Use SDK APIs

Always use the helper methods provided by the SDK (GetCartSubTotal(), GetCartQuantity(), CreatePromoDiscount(), CreateCouponDiscount(), and the IPromotionCalculationHelper lookups) instead of implementing custom lookup or discount logic.

Avoid hardcoded values

Store configurable values — discount percentages, minimum order amounts, product IDs — in Promotion Attributes (User-Defined Fields) rather than in code.

ApproachExample
RecommendedDiscount = Promotion Attribute
AvoidDiscount = 10 (hardcoded)

Add structured logging

Log promotion started, validation passed/failed, discount applied, and exceptions — but avoid excessive logging inside loops.

Handle exceptions

Always wrap business logic in exception handling and return an empty result on failure so that other promotions continue to be evaluated.

Optimize performance

  • Exit early when validation fails.
  • Avoid unnecessary iterations over cart items.
  • Minimize external service or database calls.
  • Reuse SDK helper methods whenever possible.
  • Keep promotion logic stateless and thread-safe.

13.2 Deployment Best Practices

  • Build in Release configuration.
  • Test extensions before deployment.
  • Synchronize extensions immediately after deployment.
  • Version extension assemblies using semantic versioning.
  • Validate execution logs after every deployment.
  • Maintain deployment history and release notes.

13.3 Configuration Best Practices

  • Use descriptive names for promotion types and promotions.
  • Keep the ClassName consistent — and case-matched — across code and Admin.
  • Prefer configurable user-defined fields over hardcoded values.
  • Validate and test promotion settings in a non-production environment before activating them.

13.4 Common Issues Reference

IssuePossible CauseResolution
Promotion not executingExtension not synchronizedSynchronize the extension
Promotion type not foundIncorrect Class NameVerify Admin configuration
Discount not appliedIsApplied not setSet result.IsApplied = true
No execution logsLogging not implementedConfigure IExtensionLogger
Coupon ignoredInvalid or expired couponVerify coupon configuration
Old logic executingPrevious DLL still loadedDeploy the latest DLL and Sync
Extension not loadedBuild or deployment failureVerify CLI deployment and dependencies

13.5 Debugging Checklist

  • Class name matches the implementation.
  • Promotion is active.
  • Coupon (if required) is valid.
  • Business conditions are satisfied.
  • Calculate() method executes.
  • PromotionCalculateResult is returned.
  • IsApplied is set to true.
  • Execution logs have been reviewed.

13.6 Performance Recommendations

For production environments:

  • Keep promotion logic lightweight; avoid long-running operations.
  • Use efficient collection lookups and the SDK's helper methods.
  • Log only meaningful business events.
  • Test promotions with large carts and multiple active promotions.

By following these best practices, developers can build reliable, high-performance promotion extensions that are easy to maintain and troubleshoot. Consistent use of the Promotions SDK, proper logging, and thorough testing ensures custom promotions integrate seamlessly with the Znode Extension Framework.

Did you find it helpful? Yes No

Send feedback
Sorry we couldn't be helpful. Help us improve this article with your feedback.