This document is for developers that are new to Litium. It is recommended that you first read the Get started section to get a general understanding.
License
A license is not required to setup a Litium installation on your local computer, but without a license file Litium will limit the number of requests per minute when the site is accessed from a machine other than localhost.
When you deploy to test/stage/prod environments you will need a license-file:
Technologies used
-
Litium CDN powered by Fastly handles all requests when the site has been deployed to Litium cloud.
-
Client side frameworks
-
Litium react accelerator is built with Next.JS and React as a single page application.
-
Litium MVC accelerator is built as a Microsoft .NET MVC on .NET 6, with dynamic parts built with React.
-
Angular is used to build the SPA that is the Litium backoffice.
-
The database used is Microsoft SQL Server.
-
The search engine Litium search is built on Elasticsearch.
-
Redis in-memory data store is used for:
- Distributed cache
- Distributed locks prevent other applications and threads from accessing a section of code that should not have multiple simultaneous executions.
- Service bus
- Session management
- The Redis-task on GitHub has a sample on implementing distributed cache and lock
-
Automapper is used to map data between similar objects, mainly to map data from the general object to MVC ViewModels, example:
// 1. Register mapping:
cfg.CreateMap<PageModel, ArticleViewModel>()
.ForMember(x => x.Title, m => m.MapFromField(PageFieldNameConstants.Title))
.ForMember(x => x.Text, m => m.MapFromField(PageFieldNameConstants.Text))
.ForMember(x => x.Image, m => m.MapFrom<ImageModelResolver>());
// 2. Then use:
var articleViewModel = pageModel.MapTo<ArticleViewModel>();
Data modeling, also known as the field framework
All Litium objects share a common data modelling framework called the Field framework. In this framework developers can create custom datatypes and field templates to represent different objects. An important task at the beginning of every project is to define the data models to use, and set which information needs to be stored for all entities (e.g. customers, pages or products).
All fields in the field framework can be localized, meaning that a value for the field can be set for all languages of the installation.
Read more about data modelling
Field types and fields
-
A field type defines the datatype for a field (e.g. date, number or string), field types are global and all field types are available in all areas of the Litium installation. Developers can also create custom field types.
-
A field is an instance of a field type (i.e. the field brand is of fieldtype text). Fields are created and scoped for a specific area.
Fields created by the MVC accelerator are defined in code, see folder \Src\Litium.Accelerator\Definitions\
in the MVC accelerator solution.
Example of a field defined in code:
new FieldDefinition<CustomerArea>("SocialSecurityNumber", SystemFieldTypeConstants.Text)
{
CanBeGridColumn = true, // Can be added as column to customer lists in Backoffice
CanBeGridFilter = true, // Can be used to filter customer lists in Backoffice
MultiCulture = false, // Should a field value be stored for every language?
}
Functional field types
As mentioned above the field type defines the data type for a field (e.g. date, number or string), but some field types do not only store data to be viewed, instead they have special functions or store references.
Pointer
The pointer field type is used to select a specific entity, i.e. selecting a product for a news page or selecting a reseller organization for a product.
- A pointer field can be single or multi select.
Example of a pointer field defined in code:
new FieldDefinition<BlockArea>(BlockFieldNameConstants.Link,
SystemFieldTypeConstants.Pointer)
{
Option = new PointerOption { EntityType = PointerTypeConstants.WebsitesPage }
}
Read more about pointer fields
Multifield
The multifield acts as a container for other fields (multiple fields can be added).
The multifield is also one of few field types (option-fields can be set to multi-select) that can be defined as an array. This brings a lot of value when you develop custom field types since you never have to implement array support yourself (like field UI, serialization etc.) for arrays, if a user want to use it as an array they can just wrap it in a multifield.
Say that you want to create a slideshow on the startpage, then you could create a multifield called Slider and add 3 fields to it:

By also defining the Slider-field as array, any number of slides can be added to the slideshow.
Example of the slide-multifield defined in code:
new FieldDefinition<BlockArea>(FieldConstants.Slide,
SystemFieldTypeConstants.MultiField)
{
Option = new MultiFieldOption {
IsArray = true,
Fields = new List<string>() {
FieldNameConstants.ImagePointer,
FieldNameConstants.LinkText,
FieldNameConstants.LinkToPage
}
}
}
Read more about multi fields
Field templates
All entities that use the field framework (e.g. person, group, product) are created using a field template. The field template is a definition of fields that are visible when the entity is edited in backoffice.
Note that the field template does not define the fields that can be added to an entity, only which fields are visible. The acutal data is always added to the entity and is not removed/changed if the entity switches template.
Fields in a field template are grouped together in field groups.
Example of a field defined in code:
new PersonFieldTemplate("MyPersonTemplate")
{
FieldGroups = new []
{
new FieldTemplateFieldGroup()
{
Id = "General",
Collapsed = false,
Fields =
{
SystemFieldDefinitionConstants.FirstName,
SystemFieldDefinitionConstants.LastName,
SystemFieldDefinitionConstants.Email,
"SocialSecurityNumber"
}
}
}
}
MVC display templates
In Litium the entities product, category and page have landing pages. To display these landing pages you need to connect the field templates of these entities to MVC Controllers, this connection is the display template.
If you inspect the field template of the Article-page in the settings of Litium backoffice you can see that it specifies the Index
-action of the ArticleController
as display template.
Baseproduct and variant URL
This setting in the field template for products, defines if URL should be set on the baseproduct or the variant.
- A variant level url will give every variant of the baseproduct a unique url, while a baseproduct url will have a single url that will be shared between all variants.
- When using a variant level url it is possible to define how the variants of a baseproduct should be grouped in listings, the Accelerator will in default setup group varians by color so that a baseproduct with 6 variants (3 sizes in 2 colors) will only have two items in search results and listings (one for each color).
Dependency injection
Using dependency injection in Litium is simple, all you need to to is add an attribute to an object and Litium will automatically register all instances of that object making it available for constructor injection.
Abstraction vs. implementation
The Litium platform separates abstractions (interfaces/abstract classes) from implementations in different assemblies. The IPriceCalculator
-interface is for example located in the Litium.Abstractions
assembly:
[Service(ServiceType = typeof (IPriceCalculator))]
[RequireServiceImplementation]
public interface IPriceCalculator
But its implementation is in the Litium.Application
assembly:
[Service(FallbackService = true)]
public class PriceCalculatorImpl : IPriceCalculator
- Litium provides default implementations for all abstractions configured as
FallbackService
- All default implementations have a
Impl
-suffix and the fallback-attribute
- By adding your own implementation of an abstraction you replace the default Litium Fallback-implementation
- The
RequireServiceImplementation
-attribute on the interface prevents developers from adding a reference to the Abstraction without also referencing an Implementation (Litium will not start if an abstraction with this attribute is missing implementations).
- During development you should always reference the abstractions, never the implementations directly
Service registration
Say that you have an interface called IDessert
that has the Service
-attribute, any class that implements this interface will be automatically registered:
[Service(ServiceType = typeof(IDessert), Lifetime = DependencyLifetime.Singleton)]
public interface IDessert
{
void Serve();
}
The implementation may be placed anywhere in the solution (if there are multiple implementations you can use service factory or named service to instruct Litium on which implementation to resolve):
public class IceCream : IDessert
{
public void Serve()
{
// Serve some ice cream
}
}
Example on injecting the abstraction in a class constructor:
public class Waiter
{
public Waiter(IDessert dessert)
{
dessert.Serve();
}
}
The Service
-attribute of the IDessert
-interface above also has a Lifetime
-parameter (if not specified it will default to DependencyLifetime.Singleton
), available options are:
DependencyLifetime.Singleton
: All users will receive the same single instance from the container for the lifetime of the Litium application
DependencyLifetime.Scoped
: The same instance is used within the current scope:
- The entire web request
- The execution of a scheduled task
DependencyLifetime.Transient
: A new instance will be created each time the service is requested from the container
Service decorator
In many cases you do not want to replace a class or interface entirely, perhaps you only need to modify input or output of a single method, then you can use a service decorator.
Using a service decorator could be described as getting the benefits of inheritance but at the same time the benefits of only referencing abstractions.
You can read more about service decorator but the best way to really understand the service decorator is to complete the service decorator development task on GitHub.
Risks with dependency injection
- It may not be obvious to others if you implement and override functionality. Use clear project structures and naming to avoid confusion
- When data is stored in a class registered as
DependencyLifetime.Singleton
this data is only instanciated once and then re-used until Litium restarts. This is a big risk if the data need to be fresh (as with user data). This is also a risk if you use Scoped
or Transient
since another developer may inject and store your Transient
-class in their Singleton
, so try to keep your code stateless to minimize risk.
Read more about service registration
Litium architecture
This section will introduce you to some of Litiums common technical areas.
Background jobs
Use Litium.Scheduler.SchedulerService
to create jobs that run in the background.
Jobs are stored in the database and will be executed even if the application restarts.
var fiveSecondsFromNow = DateTimeOffset.Now.AddSeconds(5);
_schedulerService.ScheduleJob<TestJobService>(
x => x.RunMyJob(), // <-- You can also pass parameters here!
new ScheduleJobArgs { ExecuteAt = fiveSecondsFromNow }
);
Scheduled jobs
Implement the ICronScheduleJob
-interface to create a job that run at a set interval:
[CronScheduler("Litium.Accelerator.Services.RandomNumberLogger", DefaultCronExpression = CronEveryMinute)]
public class RandomNumberLogger : ICronScheduleJob
{
public const string CronEveryMinute = "0 0/1 * 1/1 * ? *";
private readonly ILogger<RandomNumberLogger> _logger;
public RandomNumberLogger(ILogger<RandomNumberLogger> logger)
{
_logger = logger;
}
public async ValueTask ExecuteAsync(object parameter, CancellationToken cancellationToken = new())
{
_logger.LogDebug($"Your random number this minute is: {new Random().Next(100)}");
}
}
When a scheduled job has been created it can be adjusted with new parameters and scheduling in appsettings.json
:
"Policy": {
"Litium.Accelerator.Services.RandomNumberLogger": { "CronExpression": "0/10 * * * * ?" }
Startup jobs
Sometimes you need to run code every time the application starts, example:
- Data seeding (DefinitionSetup.cs)
- Register event subscriptions
To run code on startup just add the Autostart
-attribute:
[Autostart]
public class StartupLogger
{
public StartupLoggerDemo(ILogger<StartupLogger> logger)
{
logger.LogDebug("This code in the constructor will run every time Litium starts");
}
}
If your startup code takes time to run it will block and delay the startup, in this case it is better to run it asynchronously. Keep the AutoStart
-attribute and add the IAsyncAutostart
-interface to execute code in the StartAsync
-method:
[Autostart] // <-- Note that the autostart attribute is still required for IAsyncAutostart to work
public class AsyncStartupLogger : IAsyncAutostart
{
private readonly ILogger<AsyncStartupLogger> _logger;
public AsyncStartupLogger(ILogger<AsyncStartupLogger> logger)
{
_logger = logger;
_logger.LogDebug("This code will run synchronously (blocking) when litium starts");
}
public async ValueTask StartAsync(CancellationToken cancellationToken)
{
_logger.LogDebug("This code will run asynchronously in the background (non-blocking) when litium starts");
}
}
Read more about application lifecycle
Events
In Litium all events are handled by Litium.Events.EventBroker
.
To create a custom event you implement the IMessage
-interface, and if you need to pass data also inherit from EventArgs<>
:
public class MyEvent : EventArgs<Currency>, IMessage
{
public MyEvent(Currency item) : base(item)
{
}
}
To publish (trigger) an event just constructor inject EventBroker
and use the Publish
-method:
public class MyEventPublisher
{
private readonly EventBroker _eventBroker;
public MyEventPublisher(EventBroker eventBroker)
{
_eventBroker = eventBroker;
}
public void SendEvent()
{
_eventBroker.Publish(new MyEvent(new Currency("SEK")));
}
}
To listen to an event just register a subscription with the EventBroker
. You can use AutoStart to make sure that the registration is done every time Litium re-starts:
public class MyEventSubscriber : IAsyncAutostart
{
private readonly IApplicationLifetime _applicationLifetime;
private readonly EventBroker _eventBroker;
private readonly ISubscription<MyEvent> _subscription;
public MyEventSubscriber(EventBroker eventBroker, IApplicationLifetime applicationLifetime)
{
_eventBroker = eventBroker;
_applicationLifetime = applicationLifetime;
}
public ValueTask StartAsync(CancellationToken cancellationToken)
{
var subscription = _eventBroker.Subscribe<MyEvent>(ev =>
{
// This code will execute every time MyEvent is published
var currency = ev.Item;
});
_applicationLifetime.ApplicationStopping.Register(() =>
{
// Application is about to shutdown, unregister the event listener
subscription.Dispose();
});
return ValueTask.CompletedTask;
}
}
Important notes on events
-
In a load-balanced environment an event normally triggers only on the current server. To also trigger subscriptions on remote servers you need to adjust EventScope
when publishing the event. When using remote events Litium will use service bus in the background to send the event.
-
The event system is used by Litium to update cache and search index when data is modified. You may modify the application database directly (at own risk!), but if you do you need to manually clear cache and rebuild indexes for changes to take effect in the UI.
-
Most events can be subscribed to from outside the Litium application using webhooks, you can use Litium Swagger to get a list of the events that support webhooks.
Read more about event handling
SecurityContextService
SecurityContextService is used to get and set information about the current user context.
Check if the current user is logged in or not:
var currentUserIsLoggedIn = _securityContextService.GetIdentityUserSystemId().HasValue;
if(currentUserIsLoggedIn)
{
var personSystemId = _securityContextService.GetIdentityUserSystemId();
var currentlyLoggedInPerson = _personService.Get(personSystemId.Value);
}
Litium will always execute code with the permissions of the currently logged in user. There may be situations when you need to change this behaviour, for example during integrations that will need to write information even though the code is executing withouth authentication:
// Temporarily elevate permissions
// The system user account can perform any update in the api
using (_securityContextService.ActAsSystem("My custom integration task"))
{
// Do stuff here that the current user cannot normally do
}
// Temporarily remove permissions (execute code as anonymous user even if logged in)
using (_securityContextService.ActAsEveryone())
{
// Do stuff here that the need to be done without custom permissions
}
SecurityContextService
can also execute code as a specific person, this is safer than using system-privileges since executing code as a user having only the required permissions will prevent unintended changes:
if (person != null && !string.IsNullOrEmpty(person.LoginCredential.Username))
{
var claimsIdentity = _securityContextService.CreateClaimsIdentity(
person.LoginCredential.Username, person.SystemId);
using (_securityContextService.ActAs(claimsIdentity))
{
// Do stuff here as any provided person
}
}
Object validation
You can validate updates to all entities in Litium with custom validations:
-
Implement the general IValidationRule
-interface to trigger a validation on all entity updates
-
An easier way is to inherit ValidationRuleBase<>
(that implements the interface) to trigger your validation only when a specific type changes, for example Currency
in this example:
public class ValidationExample : ValidationRuleBase<Currency>
{
public override ValidationResult Validate(Currency entity, ValidationMode validationMode, CultureInfo culture)
{
var result = new ValidationResult();
if(entity.TextFormat != "C0")
{
result.AddError("TextFormat", "Need to be C0")
}
return result;
}
}
Try the development task on GitHub to implement a custom validation.
Logging
Constructor inject ILogger<Type>
in a class to write to the application log, then write to the log with:
_logger.LogTrace("Creating visitor group");
- Litium uses NLog for application logging.
- Configure log outputs in the file
\Src\Litium.Accelerator.Mvc\NLog.config
, for example to:
- Write error-level logs to a separate file
- Write all logs from a specific class to a separate file
- Skip logging of non-relevant messages
Entity model
Entities (e.g. person, group or page) in Litium are accessed and modified using the entitys service.
- The service is named as the entity with the Service-suffixe (i.ex. PageService)
- The service contains methods to get, create, update and delete the entity
- The service is accessed using dependency injection
Example:
public abstract class ChannelService : IEntityService<Channel>, IEntityAllService<Channel>
{
public abstract void Create(Channel channel);
public abstract void Delete(Channel channel);
public abstract Channel Get(Guid systemId);
public abstract IEnumerable<Channel> Get(IEnumerable<Guid> systemIds);
public abstract IEnumerable<Channel> GetAll();
public abstract void Update(Channel channel);
}
Modify entity
-
When an entity is retreived from Litium it is always in read-only mode (because cached entities should be non modifiable)
-
If you want to modify the entity you need to create a new writable version by calling MakeWritableClone()
then use the entity service to update, example:
person = person.MakeWritableClone();
person.LoginCredential.NewPassword = newPassword;
_personService.Update(person);
Data service
DataServcie
gives developers direct access to the Litium database, to query or modify data.
Try the data service development task on GitHub to learn more.
Batching data
Use Litium.Data.DataService.CreateBatch
to modify data. CreateBatch
can be used to do multiple modifications in a single database transaction. If any part of the trancaction scope fail the entire transaction is rolled back making CreateBatch
ideal for dependent modifications.
As an example, the code below replaces a BaseProduct, but the old BaseProduct will not be deleted unless the new BaseProduct is created without errors:
// (code below is simplified for readability)
var baseProductToDelete = _baseProductService.Get("BP1");
var replacementBaseProduct = new BaseProduct("BP2");
using (var db = _dataService.CreateBatch())
{
db.Delete(baseProductToDelete);
db.Create(replacementBaseProduct);
// Nothing is persisted in DB until Commit finalizes the transaction.
// If an exception is thrown in Create() the entire transaction
// is rolled back.
db.Commit();
}
Querying data
Use Litium.Data.DataService.CreateQuery
to query data directly from the database.
IMPORTANT - Never use CreateQuery
on a public site without also adding a cache. When you use DataService
to retreive data you bypass the built in Litium cache.
Example:
using (var query = _dataService.CreateQuery<BaseProduct>(opt => opt.IncludeVariants()))
{
var q = query.Filter(filter => filter
.Bool(boolFilter => boolFilter
.Must(boolFilterMust => boolFilterMust
.Field("MyField", "eq", "MyValue")
.Field("MyField2", "neq", "MyValue"))
.MustNot(boolFilterMustNot => boolFilterMustNot
.Id("neq", "my_new_articlenumber"))))
.Sort(sorting => sorting
.Field("MyField3", CultureInfo.CurrentCulture, SortDirection.Descending));
int totalRecords = q.Count();
List<BaseProduct> result = q
.Skip(25)
.Take(20)
.ToList();
}
The full code-sample
Web APIs
The Litium .NET API contains all functionality and extension points that are available to developers. But the .NET API is only available when functionality is built as part of the .NET solution.
If you want to develop solutions as external applications and systems that can be modified and scaled without any modification or downtime of the critical e-commerce website you have to develop integrations to Litium. Web API is the most common way of connecting and Litium has made it easy to create custom secure Web APIs, Litium also has several built in Web APIs available:
- Storefront API is a GraphQL API uswed by headless presentation laysers like the React accelerator
- Litium Connect is a collection of high level APIs, grouped by business domain
- Admin Web API is a low level API containing most of the functionality of the .NET API
- Accelerator Web API is the Web API used by the React frontend in the Accelerator MVC Website
Read more about the APIs
Web API security
The security system that is part of the built in Web APIs is also available when building custom Web APIs. It is based on Service Accounts which is a type of account that can only connect to Web API but not login to the Litium backoffice.

To secure a custom API you only need to add one attribute (or both) to a controller class (or just to a single controller action):
Read more about web API
Admin web API
The main benefit of the Admin Web API is that it functionally covers almost all of the .NET API, but this also means that changes made in the .NET API will also change the Admin Web API.
Litium connect
Litium Connect is a collection of APIs grouped by business domain:
- Litium connect ERP API: Contains functionality needed when building an integration to ERP systems, covering for exampe batch data imports, payments and order fulfillment.
- Litium connect Payments API: Used to connect payment service providers such as Klarna, Adyen etc. to authorize/capture/refund payments
- Litium connect Shipments API: Used to connect Litium to warehouse and logistics systems
(additional APIs for new business domains will be added in the future)
A major benefit of Litium connect APIs is that they have versioning that is independent of Litium platform versioning.
Litium
|
ERP API
|
Payments API
|
Shipments API
|
7.4 |
1.0 |
- |
- |
7.6 |
2.0 |
- |
- |
7.7 |
2.0 |
- |
- |
8.0 |
2.0 |
1.0 |
1.0 |
By looking at this version table we can see that an ERP integration built for a Litium 7.6 installation will still work when the customer upgrades to Litium 8. The integration could also be re-used for multiple installations running on different versions between 7.6-8.X.
As a general recommendation you should prefer using Litium Connect where suitable and use the Admin Web API as supplement where needed:
- Litium connect (being a high level API) usually requires fewer API-calls than the Admin Web API to perform an action
- Litium connect versioning makes integrations more stable and usually a platform upgrade will not require modifications to the integration
Read more about the connect APIs
Accelerator web API
The source code for the Accelerator Web API used by React is available in the MVC project in folder Controllers\Api
.
Web hooks
Both the Admin Web API and Litium Connect allow external systems to register themselves to be notified every time a specific event occurs. These subscriptions are registered using webhooks and Litium will call all registered external endpoints when an event is triggered.
Read more about web hooks
Swagger
All Web APIs have Swagger API documentation that is available in every installation on URL http://domain/litium/swagger (authentication is required)
Swagger covers Accelerator Web API, Litium Connect APIs and the Admin Web API:

Litium apps
A Litium App is a standalone application that communicate with the Litium application using Web API.
All Payment and Shipment providers run as Litium Apps (from Litium version 8+)
Read more about apps
MVC accelerator development
The Accelerator is structured with a clear separation of business logic from presentation to make it possible to replace the MVC UI if needed.
Read more about the MVC accelerator
Solution structure
When Litium is installed a new Visual Studio solution is created with 5 projects (an additional e-mail project is also created). This is the Accelerator source code that can be modified in the implementation project.
The more changes you make to the codebase the bigger the project will be to upgrade to new Accelerator versions, so there is a balance when doing modifications. Changes made in the Accelerator codebase are documented in the Accelerator release notes (separated from the Litium platform release notes).
Litium.Accelerator-project
The Litium.Accelerator
-project is the BLL (Business logic layer) that contains all logic separated from the UI and Web API, for example:
- ViewModels and ViewModelBuilders
- Definitions
- Services
- Validations
This project has most of the Litium NuGet references.
Litium.Accelerator.Administration.Extensions-project
Contains customizations of the Litium backoffice UI, for example custom settings pages that are part of the Accelerator.
Litium.Accelerator.Elasticsearch-project
Contains the implementation of Elasticsearch, modify this code if you need to:
- Modify what data is stored in the index
- Modify how queries are executed against the index data
Litium.Accelerator.FiledTypes-project
Contains custom field types for Litiums entity field framework. The Accelerator contains one custom field called FilterField that is used to control which fields should be included in category filters.
Modify this project if you want to add your own custom field types.
Litium.Accelerator.MVC-project
The web project containing stanard MVC files such as Controllers and Razor Views, but also:
- The Web API controllers used by the Accelerator React UI.
- A
/Definitions
-folder with the links between Field Templates and controllers
- A
/Client
-folder with all styles and JavaScript
- Configuration and package reference files
Read more about the MVC project
Litium.Accelerator.Email-project
This project can be found when looking in the solution folder on disk but is not included in the Visual Studio solution.
Client side project to manage e-mail templates (Order confirmation e-mail)
Zurb Foundation for Emails is used for styling.
Litium search
Litium search is the search engine used in Litium Accelerator. It is built on Elasticsearch with additional plugins and administration interface.
Litium search runs as a separate application and a single search engine is used for all web servers in a multiserver installation.
A new Accelerator installation contains indicies for:
- Pages
- Categories
- Products
- Purchase history

Additional search indicies can also be created if needed
Synonyms for Litium search can be added directly in Litium backoffice, by adding the sample below a query for trousers will also give search hits for pants, chinos and leggings.

Read more about LItium search
MVC Accelerator front-end
The front-end code can be found in the Client
-folder in the MVC-project.
Accelerator front-end tech stack
Read about front-end coding in the MVC accelerator
State on the client
To give the stateless Web API controllers access to the current Litium state (current page/channel/product), Litium passes that information in every request as the RequestContext
:
-
In the shared view \Shared\Framework\ClientContext.cshtml
the global object window.__litium.requestContext
is written to every rendered MVC-page in the Accelerator. If you try the command below in the browsers JavaScript console on a Accelerator-page
> window.__litium.requestContext
...you can see the current RequestContext
-object:
{
"channelSystemId": "9256cc22-2b93-4a4e-bfa9-04dc51ec4b05",
"currentPageSystemId": "2df58068-fe4b-40b9-851f-c49cd4f3f2bc",
"productCategorySystemId": "01015225-c6a8-4bac-ae17-834cf7617016"
}
2. The React http
-service is used when ajax-requests are created on the client. It serializes and attaches window.__litium.requestContext
as a header to every request:
// from \Src\Litium.Accelerator.Mvc\Client\Scripts\Services\http.js
headers: {
'litium-request-context': JSON.stringify(window.__litium.requestContext),
}
3. When requests are received on the server Litium looks for the litium-request-context
-header, and if the header is found its data is stored and made available in RequestModelAccessor
.
// from \Src\Litium.Accelerator.Mvc\Runtime\RequestModelActionFilter.cs
var siteSettingViewModel = request.Headers.GetSiteSettingViewModel();
4. Any Web API Controller (for example CheckoutController
) can then inject and use RequestModelAccessor
to get state for the request
public CheckoutController(RequestModelAccessor requestModelAccessor ...
{
...
E-commerce development
Cart
The cart contains all the information needed to create an order.
- The current users cart is kept in distributed cache
- Use the service wrapper
CartContext
to modify current users cart, CartContext
is accessed by injecting CartContextAccessor
or through the extension method HttpContext.GetCartContext()
.
Modify cart example:
await cartContext.AddOrUpdateItemAsync(new AddOrUpdateCartItemArgs
{
ArticleNumber = articleNumber,
Quantity = quantity,
});
Read more about the cart
Discounts
Discounts are stored as OrderRows with OrderRowType set to Discount and negative price, example:
|
Row type
|
Price
|
Item in cart |
Product |
400 |
25% off item |
Product discount |
-100 |
Order discount |
Order discount |
-150 |
Grand total |
|
150 |
-
A discount may be connected to a specific OrderRow (for example a product, fee or shipping row)
-
One product-row may be connected to multiple discount-rows
-
Discounts are always counted in a specific order:
- Product discounts
- Order level discounts
- Free gifts
- Fee and Shipping discounts
Read more about discounts
Payments
A payment in Litium has multiple transactions that keep track of how much money is Authorized, how much of the authorized amount that is Captured or Cancelled, and how much of the Captured amount that is Refunded.
-
Init: Litium has initialized a payment with the Payment Service Provider (PSP)
-
Authorize: The buyer has committed to pay (usually this means that money is reserved in the buyers financial institution)
-
Capture: When money is actually moved from buyer to seller. This can only be done based on a previous authorize transaction
- Certain payment methods such as Swish and Bank direct debit moves money directly, without a reservation step (an Authorize transaction is created, and immediately followed by a Capture transaction)
-
Cancel: An authorization can also be cancelled - can only be done based on a previous Authorize-transaction
-
Refund: A capture may be refunded back to the buyer - can only be done based on one or more previous Capture-transactions
Transactions also have status, a capture may for example have status pending or denied
Checkout
1. Initialize payment
The method TryInitializeCheckoutFlowAsync()
must be called called when the checkout is loaded, it:
- Sends the settings required to handle the payment to PSP (for example URLs to checkout/receipt pages)
- Creates a payment transaction in Litium with
TransactionType::Init
When changes are made to the cart after initialization the method CalculatePaymentsAsync()
has to be called, it:
- Sends the updated cart to the PSP payment App
- The PSP payment App responds with a reference to the created/updated payment
PaymentOverview
can be used to show all Payments, Transactions and TransactionRows
Read more about payments
2. Redirect/iframe to PSP
-
A Payment Service Provider (PSP) handles the transfer of money from buyer to merchant
-
In Litium all PSPs run as standalone apps (hosted in Litium cloud) that use Web API to connect to the Litium platform. This lets Litium checkout page work independenly of which PSPs are installed
-
Litium checkout page displays the payment methods that are configured for the current Channel
-
Litium checkout page supports:
-
Hosted payment pages (like Paypal) where the buyer is redirected to PSP site
-
Iframe checkouts where an iframe is embedded in the checkout page, this type has two variants:
-
Full checkout including the collection of customer information inside the iframe (like Klarna/Svea)
-
Collecting payment information only inside the iframe (like Adyen), then Litium collects the customer information separately
Read more about order placement
3. Payment confirmation
The SalesOrder is created and saved to the database when a PSP App notifies Litium that money is reserved for a payment
The SalesOrder contains items (order rows) and the information required to fulfill the order:
- Addresses (delivery and billing)
- Customer information
- A single payment
- One or more shipments
A payment Authorize-transaction is created, it has a reference to the previous Init-transaction that was created during payment intialization
Use OrderOverviewService
to get the OrderOverview
to access all payments, shipments and returns connected to a SalesOrder
.
Read more
Order placement
Order fulfillment
Sales data modelling
Order fulfillment
When an order is placed it needs to be fulfilled (shipped).
This process is normally done through integration to an ERP system:
-
Litium sends a placed order to ERP to start the fulfilment process
-
ERP notifies Litium when a shipment is ready to send
-
Litium creates a new Shipment in Init state and calculates the value of the shipment
-
The shipment state is changed to Processing, in the shipment state transition event the payment is captured:
-
A payment Capture-transaction is created in Litium
-
Money is captured through the PSP App
-
PSP notifies Litium through a callback that the payment is captured
-
Litium sets the shipment to state ReadyToShip and the ERP is notified
-
ERP notifies Litium when the delivery has been handed over to a delivery provider (e.g. DHL)
-
Litium sets shipment state to Shipped
Read more
Order fulfillment
Shipments
State transitions
Shipment states

-
Init is set when the shipment is created
-
Setting a shipment to Processing will trigger payment capture
-
ReadyToShip is set when all payments for a shipment are captured
-
Shipped is set when the shipment is handed over to the shipping company (not when delivered to buyer)
A shipment can also go from Processing to Cancelled
SalesOrder states

-
The order is set to Confirmed when at least one payment is guaranteed (has an Authorize-transaction)
-
An order can be only partially fulfilled but still completed, it is set to Completed when
State transition validations
Add StateTransitionValidationRules
to make sure that Orders/Shipments meet the requirements needed to change state.
public class ProcessingToCompletedCondition : StateTransitionValidationRule<SalesOrder>
{
// (non relevant code has been removed)
public override string FromState => Sales.OrderState.Processing;
public override string ToState => Sales.OrderState.Completed;
public override ValidationResult Validate(SalesOrder entity)
{
var result = new ValidationResult();
var order = _orderOverviewService.Get(entity.SystemId);
if(!HasAllShipmentsShipped(order))
{
result.AddError("Shipment", "All shipments are not shipped.");
}
return result;
}
}
State transition events
Add event listeners to act when Orders/Shipments change state.
// (This code can be found in the tAccelerator)
[Autostart]
public class SalesOrderEventListener : IAsyncAutostart
{
// (non relevant code removed)
ValueTask IAsyncAutostart.StartAsync(CancellationToken cancellationToken)
{
_eventBroker.Subscribe<SalesOrderConfirmed>(x => _stockService.ReduceStock(x.Item));
_eventBroker.Subscribe<SalesOrderConfirmed>(x => _mailService.SendEmail(/* params */);
return ValueTask.CompletedTask;
}
}
Tags
A tag is a string key added to an order, shipment or payment
-
Events are raised when tags are added or removed
-
Tags can for example be used to set values that can be checked in state transition validations
-
Tags are used in the Accelerator to handle the B2B order approval flow by setting a "Waiting for approval"-tag on orders
Database management
Litium requires a Microsoft SQL Server 2019 database.
The command-line donet tool litium-db is used to manage the database, it can be used to:
- Setup new Litium application databases
- Upgrade a Litium application database to a specific version, either by connecting directly to the database or by generating scripts for later upgrade
- Create new admin users in the database
Litium versioning and upgrade
All Litium custumers have independent installations and many of these are not yet upgraded to the latest version 8. A short background is useful to better understand the architecture when maintaining older installations.
The current Litium version 8 has a SPA-backoffice and a modern, testable, service based architecture built using the latest version of .NET. Litium version 4 had an outdated architecture, dependencies on .NET Framework and needed a full rewrite, but instead of rewriting the entire platform at once this was done in steps.
- Litium 5 (2016) was the starting point of the new Litium where the product database was rewritten into the new Products-area (PIM)
- Litium 6 (2018) was the rewrite of the areas Media and Customers
- Litium 7 (2018) was the rewrite of the Website-area (CMS) and the new Globalization-area was added
- Litium 8 (2021) was the final step with the last area Sales being rewritten, with this final step the last dependencies on some old technology was lost:
- The old .NET Framework could be replaced by .NET (Core) 6
- The old Lucene search engine could be removed entirely from the codebase
So depending on which version of Litium you work with you will find different architectures in the different areas depending on which has been rewritten in that version.
Coming functionality and roadmap
You can find information about features currently in development in the roadmap.
Upgrading
A Litium Solution is upgraded in three steps:
-
The Litium platform is upgraded using NuGet update to upgrade all packages to the latest version
-
The database is upgraded using the Litium db-tool
-
Upgrading Litium Accelerator is manual
-
This is likely a big task depending on how big the gap is between the old and new version
-
The latest released Accelerator can be used as reference but the task to merge the modified solution with the new Accelerator sourcecode is still likely a big task
Read more on how to upgrade to Litium 8