Customize order creation

Advanced license required

Features described on this page require the Xperience by Kentico Advanced license tier.

The order creation service provides several mapper interfaces that allow you to customize the order creation process without modifying core functionality. Use these interfaces to add custom fields to orders, customers, addresses, and order items, or to modify data before it’s persisted to the database.

Customization scenarios and overview

Consider implementing custom mappers when you need to:

  • Store additional business-specific data on orders, customers, or addresses.
  • Calculate or derive values during order creation (loyalty points, customer segments, etc.).
  • Integrate with external systems and store integration metadata.
  • Apply custom business rules before persisting order data.
  • Maintain audit trails or custom tracking information.

Mapper usage in order creation lifecycle

The following diagram illustrates where each available mapper is called in the order creation process:

Order creation process with mapper integration points

Usage notes

  • All mappers are optional. If no mapper is registered, the default behavior is used.
  • ICustomerInfoMapper<TOrderData> is only invoked when creating new customer records. If an existing customer is found (by member ID or email), the mapper is not called. To update existing customer data, implement separate logic after order creation.

Customer information mapping

Implement ICustomerInfoMapper<TOrderData> to add custom fields to customer records or modify customer data during order creation.

C#
Example - Custom customer mapper

                     /// <summary>
                     /// Maps custom fields to customer records during order creation.
                     /// Only called when creating new customers, not for existing ones.
                     /// </summary>
                     public class CustomCustomerInfoMapper : ICustomerInfoMapper<CustomOrderData>
                     {
                         public CustomerInfo PopulateInfo(CustomerInfo customerInfo, CustomOrderData orderData)
                         {
                             // Custom extension methods to populate additional customer fields
                             customerInfo.SetLoyaltyPoints(orderData.LoyaltyPoints);
                     
                             return customerInfo;
                         }
                     }
                     
                     public static class CustomerInfoExtensions
                     {
                         public static decimal? GetLoyaltyPoints(this CustomerInfo customer)
                         {
                             return customer.GetValue<decimal?>("CustomerLoyaltyPoints", null);
                         }
                     
                         public static void SetLoyaltyPoints(this CustomerInfo customer, decimal points)
                         {
                             customer.SetValue("CustomerLoyaltyPoints", points);
                         }
                     }

Important: Custom mappers are called only when creating new customer records. If an existing customer is found (by member ID or email), the mapper is not invoked. To update existing customer data, you need to implement separate customer update logic outside the order creation process. After order creation, you have access to the ID of the created order, which you can use to retrieve the corresponding customer.

C#
Access customer objects via orderId

// Gets the order
var order = await orderInfoProvider.GetAsync(orderId, cancellationToken);

// Gets the associated customer
var customer = await customerInfoProvider.GetAsync(order.OrderCustomerID, cancellationToken);

Register the mapper in the service container.

C#
Program.cs

// Registers the mapper in the application service container
builder.Services.AddTransient<ICustomerInfoMapper<CustomOrderData>, CustomCustomerInfoMapper>();

Order information mapping

Implement IOrderInfoMapper<TOrderData, TPriceCalculationResult> to add custom fields to order records. This mapper has access to both the order data and the price calculation result, allowing you to store calculated values or metadata with the order.

C#
Example - Custom order mapper

                     /// <summary>
                     /// Maps custom fields to order records.
                     /// Has access to both order data and price calculation results.
                     /// </summary>
                     public class CustomOrderInfoMapper : IOrderInfoMapper<CustomOrderData, PriceCalculationResult>
                     {
                         public OrderInfo PopulateInfo(
                             OrderInfo orderInfo,
                             CustomOrderData orderData,
                             PriceCalculationResult calculationResult)
                         {
                             // Add custom logic to populate additional order fields
                     
                             // Custom extension methods to populate additional customer fields
                             orderInfo.SetAdditionalNotes(orderData.AdditionaNotes);
                     
                             return orderInfo;
                         }
                     }
                     
                     public static class OrderInfoExtensions
                     {
                         public static void SetAdditionalNotes(this OrderInfo order, string additionaNotes)
                         {
                             order.SetValue("OrderAdditionalNotes", additionaNotes);
                         }
                     
                         public static string? GetAdditionalNotes(this OrderInfo order)
                         {
                             return order.GetValue<string?>("OrderAdditionalNotes", null);
                         }
                     }

Register the mapper in the service container.

C#
Program.cs

// Registers the mapper in the application service container
builder.Services.AddTransient<IOrderInfoMapper<CustomOrderData, PriceCalculationResult>, CustomOrderInfoMapper>();

Address mapping

Implement ICustomerAddressInfoMapper<TAddressDto> or IOrderAddressInfoMapper<TAddressDto> to add custom fields to address records. Both customer addresses and order addresses can be extended independently.

C#
Example - Custom address mapper

                     /// <summary>
                     /// Maps custom fields to order address records.
                     /// </summary>
                     public class CustomAddressInfoMapper : IOrderAddressInfoMapper<CustomAddressDto>
                     {
                         public OrderAddressInfo PopulateInfo(
                             OrderAddressInfo orderAddressInfo,
                             CustomAddressDto addressDto)
                         {
                             // Custom extension methods to populate additional customer fields
                             orderAddressInfo.SetAddressDeliveryNote(addressDto.DeliveryNotes);
                     
                             return orderAddressInfo;
                         }
                     
                         public CustomAddressDto PopulateDto(CustomAddressDto addressDto, OrderAddressInfo orderAddressInfo)
                         {
                             // Place custom mapping from the database entity to the DTO object here
                             return addressDto;
                         }
                     }
                     
                     public static class OrderAddressInfoExtensions
                     {
                         public static void SetAddressDeliveryNote(this OrderAddressInfo address, string notes)
                         {
                             address.SetValue("OrderAddressDeliveryNotes", notes);
                         }
                     
                         public static string? GetAddressDeliveryNote(this OrderAddressInfo address)
                         {
                             return address.GetValue<string?>("OrderAddressDeliveryNotes", null);
                         }
                     }

Address comparison: When checking for duplicate customer addresses, the service uses all fields including custom fields in the comparison. Ensure your custom fields are appropriate for duplicate detection, or implement custom comparison logic if needed.

Register the mapper in the service container.

C#
Program.cs

// Registers the mapper in the application service container
builder.Services.AddTransient<IOrderAddressInfoMapper<CustomAddressDto>, CustomAddressInfoMapper>();

Order item mapping

Implement IOrderItemInfoMapper<TOrderData, TPriceCalculationResult> to add custom fields to order items. This is useful for storing product-specific metadata or configuration data with each line item.

C#
Example - Custom order item mapper

                     /// <summary>
                     /// Maps custom fields to order item records.
                     /// Useful for product configuration, warranty selections, etc.
                     /// </summary>
                     public class CustomOrderItemInfoMapper : IOrderItemInfoMapper<CustomOrderData, PriceCalculationResult>
                     {
                         public OrderItemInfo PopulateInfo(
                             OrderItemInfo orderItemInfo,
                             CustomOrderData orderData,
                             PriceCalculationResult calculationResult,
                             ProductIdentifier productIdentifier)
                         {
                             // Add custom logic to populate additional order item fields
                     
                             orderItemInfo.SetOrderItemWarranty(24);
                     
                             return orderItemInfo;
                         }
                     }
                     
                     public static class OrderItemInfoExtensions
                     {
                         public static void SetOrderItemWarranty(this OrderItemInfo address, int notes)
                         {
                             address.SetValue("ItemWarrantyMonths", notes);
                         }
                     
                         public static int? GetOrderItemWarranty(this OrderItemInfo address)
                         {
                             return address.GetValue<int?>("ItemWarrantyMonths", null);
                         }
                     }

Register the mapper in the service container.

C#
Program.cs

// Registers the mapper in the application service container
builder.Services.AddTransient<IOrderItemInfoMapper<CustomOrderData, PriceCalculationResult>, CustomOrderItemInfoMapper>(); 

Price calculation customization

Implement IPriceCalculationRequestMapper<TPriceCalculationRequest, TOrderData> to add custom data to price calculation requests before prices are calculated. This allows you to influence the pricing calculation with order-specific context.

C#
Example - Custom calculation request mapper

                     /// <summary>
                     /// Maps custom data to price calculation requests.
                     /// Allows passing order-specific context to pricing calculations.
                     /// </summary>
                     public class CustomCalculationRequestMapper : IPriceCalculationRequestMapper<PriceCalculationRequest, CustomOrderData>
                     {
                         public PriceCalculationRequest PopulateCalculationRequest(
                             PriceCalculationRequest calculationRequest,
                             CustomOrderData orderData)
                         {
                             // Add custom logic to modify the calculation request
                     
                             // Example: Add customer tier for tiered pricing
                             // Example: Add promotional context or B2B contract information
                     
                             return calculationRequest;
                         }
                     }

For more information on customizing the price calculation pipeline itself, see Customize price calculation.

Register the mapper in the service container.

C#
Program.cs

// Registers the mapper in the application service container
builder.Services.AddTransient<IPriceCalculationRequestMapper<PriceCalculationRequest, CustomOrderData>, CustomCalculationRequestMapper>();

Extend order data objects

To use custom fields in your mappers, extend the OrderData class and use your custom class throughout your order creation implementation:

C#
Example - Custom order data

                     /// <summary>
                     /// Extended order data with custom fields.
                     /// </summary>
                     public class CustomOrderData : OrderData
                     {
                         public int LoyaltyPoints { get; set; }
                         public string AdditionaNotes { get; set; } = string.Empty;
                     }

You can also extend address DTOs to add custom address fields:

C#
Example - Custom address DTO

                     /// <summary>
                     /// Extended address DTO with delivery notes field.
                     /// </summary>
                     public record CustomAddressDto : AddressDto
                     {
                         public string DeliveryNotes { get; set; } = string.Empty;
                     }

Update your controller to use the custom order data type:

C#
Example - Using custom order data

                     /// <summary>
                     /// Example controller using custom order data type.
                     /// Shows how to create orders with custom fields.
                     /// </summary>
                     public class CustomCheckoutController : Controller
                     {
                         private readonly IOrderCreationService<CustomOrderData, PriceCalculationRequest,
                             PriceCalculationResult, AddressDto> orderCreationService;
                     
                         public CustomCheckoutController(
                             IOrderCreationService<CustomOrderData, PriceCalculationRequest,
                                 PriceCalculationResult, AddressDto> orderCreationService)
                         {
                             this.orderCreationService = orderCreationService;
                         }
                     
                         public async Task<IActionResult> CreateOrder(CancellationToken cancellationToken)
                         {
                             var orderData = new CustomOrderData
                             {
                                 // Populates standard fields
                                 MemberId = 1,
                                 OrderNumber = "ORD-2024-0001",
                                 LanguageName = "en",
                                 BillingAddress = new AddressDto
                                 {
                                     FirstName = "John",
                                     LastName = "Doe",
                                     Email = "john@example.com",
                                     Line1 = "123 Main St",
                                     City = "New York",
                                     Zip = "10001",
                                     CountryID = 1,
                                     StateID = 1
                                 },
                                 OrderItems =
                                 [
                                     new OrderItem
                                     {
                                         ProductIdentifier = new ProductIdentifier { Identifier = 123 },
                                         Quantity = 2
                                     }
                                 ],
                                 
                                 // Populates custom fields
                                 LoyaltyPoints = 150
                             };
                     
                             int orderId = await orderCreationService.CreateOrder(orderData, cancellationToken);
                             return RedirectToAction("Confirmation", new { orderId });
                         }
                     }