Advanced license required
Features described on this page require the Xperience by Kentico Advanced license tier.
The system provides a streamlined way to create orders from shopping cart data using IOrderCreationService. The API automates the entire order creation workflow, including customer management, price calculation, and order persistence.
IOrderCreationService handles the complete order creation process. The API:
The order creation API uses the price calculation API internally to calculate order totals, taxes, and shipping costs. Before implementing order creation, you must implement the mandatory components for price calculation:
IProductDataRetriever<ProductIdentifier, ProductData>) – provides product information and pricing data from your product catalog.ITaxPriceCalculationStep) – calculates applicable taxes (the default implementation does not add any taxes).See Implement price calculation for complete implementation details and examples.
Without a properly configured price calculation service, the order creation process cannot calculate order totals and will fail when you attempt to create an order.
This example shows the minimal required setup to use the API:
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using CMS.Commerce;
using Kentico.Membership;
/// <summary>
/// Minimal example showing basic order creation.
/// </summary>
public class CheckoutControllerSample : Controller
{
private readonly IOrderCreationService<OrderData, PriceCalculationRequest, PriceCalculationResult, AddressDto> orderCreationService;
private readonly UserManager<ApplicationUser> userManager;
public CheckoutControllerSample(
IOrderCreationService<OrderData, PriceCalculationRequest, PriceCalculationResult, AddressDto> orderCreationService,
UserManager<ApplicationUser> userManager)
{
this.orderCreationService = orderCreationService;
this.userManager = userManager;
}
public async Task<IActionResult> CreateOrder(CancellationToken cancellationToken)
{
// Prepare order data from your checkout form
var orderData = new OrderData
{
// Identifies the authenticated user for the order, if one exists
BuyerIdentifier = BuyerIdentifier.FromMemberId(
(await userManager.GetUserAsync(User))?.Id ?? 0),
// Ensure a unique order ID
OrderNumber = "ORD-2026-0001",
// Gets the rest of the order data from the request
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
},
// Uses the billing address for shipping
ShippingAddress = null,
// Passed to IPriceCalculationService to calculate order totals
OrderItems = new[]
{
new OrderItem
{
ProductIdentifier = new ProductIdentifier { Identifier = 123 },
Quantity = 2
}
}
};
// Creates the order
int orderId = await orderCreationService.CreateOrder(orderData, cancellationToken);
// Redirects users to a confirmation page
return RedirectToAction("Confirmation", new { orderId });
}
}The service automatically:
IPriceCalculationService.When you call CreateOrder, the service executes the following steps in order:
Transaction scope
Only order entities (OrderInfo, OrderAddress, OrderItem) are created within a database transaction. If order creation fails, these entities are rolled back.
Customer entities (CustomerInfo, CustomerAddressInfo) are persisted outside the transaction. If order creation fails after customer entities are created, those customer records remain in the database even though no order is created.
Order data is represented by the IOrderData interface and its implementation OrderData. It contains:
BuyerIdentifier that identifies the buyer as either a registered member or an existing customer.IOrderItem<ProductIdentifier>.AddressDto objects.using CMS.Commerce;
/// <summary>
/// Example showing how to construct order data with all common fields.
/// </summary>
public class OrderDataExample
{
public OrderData CreateOrderData(int currentMemberId)
{
var orderData = new OrderData
{
BuyerIdentifier = BuyerIdentifier.FromMemberId(currentMemberId),
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
},
ShippingAddress = new AddressDto
{
FirstName = "John",
LastName = "Doe",
Email = "john@example.com",
Line1 = "456 Elm St",
City = "Boston",
Zip = "02101",
CountryID = 1,
StateID = 2
},
PaymentMethodId = 1,
ShippingMethodId = 2,
// Include any coupon codes entered by the customer
CouponCodes = new[] { "WINTER20" },
OrderItems = new[]
{
new OrderItem
{
ProductIdentifier = new ProductIdentifier { Identifier = 123 },
Quantity = 2
}
}
};
return orderData;
}
}Addresses are represented by the AddressDto record, which includes:
The service automatically:
Notes
The shipping address is optional. If ShippingAddress is null, the service assumes the billing address should be used for both billing and shipping purposes. Only the billing address will be created as an order address in this case.
Address comparison logic: The service uses case-insensitive, trimmed comparison when checking for duplicate customer addresses. For example, "123 Main St" and "123 MAIN ST" are considered the same address.
The service uses BuyerIdentifier to determine how to look up or create customer records:
BuyerIdentifier.FromMemberId()) – Retrieves existing customer records by member ID or creates new ones if the member doesn't have a customer record yet.BuyerIdentifier.FromCustomerId()) – Retrieves the customer record directly by customer ID.BuyerIdentifier) – Looks up existing customers by email address from the order data, or creates a new customer record if no match is found.Customer data updates
When an existing customer is found (by member ID or email), the service uses the existing customer record without updating customer information fields (such as first name, last name, email, or phone). Customer data is only populated when creating a new customer record.
If you need to update customer information during order creation, implement a custom ICustomerInfoMapper<TOrderData> to modify the customer record before it's used in the order. See Order creation customization.
Customer addresses are stored separately and reused across orders when they match existing addresses.
For signed-in members with existing customer records, you can retrieve their saved CustomerInfo and CustomerAddressInfo records using the generic info provider to pre-populate the checkout form before order creation. This is a common customization that improves the checkout experience for returning customers.
For a full working example see the Dancing Goat sample site implementation.
The order creation API uses IPriceCalculationService to compute order totals. You can customize the price calculation by:
IPriceCalculationRequestMapper<TPriceCalculationRequest, TOrderData> to add custom data to calculation requests.For more information on price calculation, see Price calculation.
The order creation service automatically persists the applied promotion candidates calculated during price calculation. All three promotion types are handled, but each is stored a little differently:
UnitPriceDiscountAmount multiplied by the item's quantity.OrderDiscountAmount as-is.You don't need to manually handle promotion data during order creation – the service extracts it from the IPriceCalculationResult and creates the appropriate order promotion records.
To query promotions applied to an existing order (for example, to display discount details on an order confirmation page or in order history), use IInfoProvider<OrderPromotionInfo>:
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CMS.Commerce;
using CMS.DataEngine;
// ...
private readonly IInfoProvider<OrderPromotionInfo> _orderPromotionInfoProvider;
public OrderPromotionQueryExamples(
IInfoProvider<OrderPromotionInfo> orderPromotionInfoProvider)
{
_orderPromotionInfoProvider = orderPromotionInfoProvider;
}
/// <summary>
/// Retrieves all promotions applied to a specific order.
/// </summary>
public async Task<IEnumerable<OrderPromotionInfo>> GetPromotionsForOrder(
int orderId,
CancellationToken cancellationToken = default)
{
// Retrieves promotion records directly by the order ID
IEnumerable<OrderPromotionInfo> orderPromotions = await _orderPromotionInfoProvider.Get()
.WhereEquals(nameof(OrderPromotionInfo.OrderPromotionOrderID), orderId)
.GetEnumerableTypedResultAsync(cancellationToken: cancellationToken);
return orderPromotions;
}For information on implementing promotion rules, see Implement catalog discounts, Implement order discounts, or Implement free shipping.
To use the service in your checkout process:
IOrderCreationService<TOrderData, TPriceCalculationRequest, TPriceCalculationResult, TAddressDto> into your controller or service.OrderData object with cart items, addresses, and selected methods.CreateOrder to process the order.using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using CMS.Commerce;
using Kentico.Membership;
/// <summary>
/// Complete example with address mapping and helper methods.
/// </summary>
public class CheckoutControllerFullExample : Controller
{
private readonly IOrderCreationService<OrderData, PriceCalculationRequest,
PriceCalculationResult, AddressDto> orderCreationService;
public CheckoutControllerFullExample(
IOrderCreationService<OrderData, PriceCalculationRequest,
PriceCalculationResult, AddressDto> orderCreationService)
{
this.orderCreationService = orderCreationService;
}
public async Task<IActionResult> ConfirmOrder(
CustomerViewModel customer,
CustomerAddressViewModel billingAddress,
ShippingAddressViewModel shippingAddress,
int paymentMethodId,
int shippingMethodId,
CancellationToken cancellationToken)
{
// Retrieve shopping cart items (implementation depends on your cart model)
var cartItems = await GetShoppingCartItems(cancellationToken);
// Prepare order data
var orderData = new OrderData
{
BuyerIdentifier = BuyerIdentifier.FromMemberId(GetMemberId()),
OrderNumber = await GenerateOrderNumber(cancellationToken),
LanguageName = GetCurrentLanguage(),
BillingAddress = MapToAddressDto(billingAddress, customer),
ShippingAddress = shippingAddress.IsSameAsBilling
? null
: MapToAddressDto(shippingAddress, customer),
PaymentMethodId = paymentMethodId,
ShippingMethodId = shippingMethodId,
OrderItems = cartItems.Select(item => new OrderItem
{
ProductIdentifier = item.ProductIdentifier,
Quantity = item.Quantity
})
};
// Create the order
int orderId = await orderCreationService.CreateOrder(orderData, cancellationToken);
// Clear the shopping cart and redirect to confirmation page
await ClearShoppingCart(cancellationToken);
return RedirectToAction("OrderConfirmation", new { orderId });
}
// Helper methods (simplified for documentation)
private Task<IEnumerable<CartItemPlaceholder>> GetShoppingCartItems(CancellationToken cancellationToken) =>
Task.FromResult(Enumerable.Empty<CartItemPlaceholder>());
private Task<string> GenerateOrderNumber(CancellationToken cancellationToken) =>
Task.FromResult("ORD-2024-0001");
private string GetCurrentLanguage() => "en";
private int GetMemberId() => User.Identity?.IsAuthenticated == true ? 1 : 0;
private Task ClearShoppingCart(CancellationToken cancellationToken) => Task.CompletedTask;
private AddressDto MapToAddressDto(
CustomerAddressViewModel address,
CustomerViewModel customer)
{
return new AddressDto
{
FirstName = customer.FirstName,
LastName = customer.LastName,
Company = customer.Company,
Email = customer.Email,
Phone = customer.PhoneNumber,
Line1 = address.Line1,
Line2 = address.Line2,
City = address.City,
Zip = address.PostalCode,
CountryID = address.CountryId,
StateID = address.StateId
};
}
}You have successfully created orders from shopping cart data. Customer records, addresses, and order items are persisted in the database, and order confirmation notifications are sent.
The order creation service provides mapper interfaces to extend orders, customers, addresses, and order items with custom fields. You can also customize how data is mapped before persistence or integrate with external systems during order creation.
Common customization scenarios include:
For detailed implementation examples and best practices, see Order creation customization.