Skip to content

Commit 0a9fbe5

Browse files
authored
Merge pull request #7511 from mattbrailsford/uc-order-number-generators
Order number generator docs
2 parents e73af8f + 75dbf41 commit 0a9fbe5

File tree

3 files changed

+308
-9
lines changed

3 files changed

+308
-9
lines changed

16/umbraco-commerce/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
* [List of notification events](key-concepts/events/list-of-notification-events.md)
7575
* [Fluent API](key-concepts/fluent-api.md)
7676
* [Order Calculation State](key-concepts/order-calculation-state.md)
77+
* [Order Number Generators](key-concepts/order-number-generators.md)
7778
* [Payment Forms](key-concepts/payment-forms.md)
7879
* [Payment Providers](key-concepts/payment-providers.md)
7980
* [Pipelines](key-concepts/pipelines.md)

16/umbraco-commerce/how-to-guides/order-number-customization.md

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,122 @@ description: Learn how to customize the default order number generated in Umbrac
44

55
# Order Number Customization
66

7-
In Umbraco Commerce, the default order number generation can be customized by implementing the `IOrderNumberGenerator` interface. This interface defines two methods: `GenerateCartNumber(Guid storeId)` and `GenerateOrderNumber(Guid storeId)`, which you can override to create a custom numbering system.​
7+
Umbraco Commerce provides flexible options for customizing order numbers to meet your business requirements. This guide covers different approaches, from template-based customization to implementing fully custom generators.
8+
9+
## Built-in Generators
10+
11+
Before implementing a custom solution, understand that Umbraco Commerce includes two built-in order number generators:
12+
13+
- **CompactSortableOrderNumberGenerator** (Recommended) - Produces compact, time-sortable identifiers with high scalability and multi-node support
14+
- **DateHashOrderNumberGenerator** (Legacy) - Date-based format maintained for backward compatibility
15+
16+
For detailed information about these generators and how they work, see the [Order Number Generators](../key-concepts/order-number-generators.md) key concepts documentation.
17+
18+
## Before Creating a Custom Generator
19+
20+
Consider these alternatives before implementing a custom generator:
21+
22+
### 1. Using Store Templates
23+
24+
You can customize order numbers through store-level templates. Templates allow you to add prefixes, suffixes, or formatting without writing any code:
25+
26+
```csharp
27+
// Configure templates via the Store entity
28+
await store.SetCartNumberTemplateAsync("CART-{0}");
29+
await store.SetOrderNumberTemplateAsync("ORDER-{0}");
30+
```
31+
32+
**Examples:**
33+
- Template: `"ORDER-{0}"` + Generated: `"22345-67ABC"` = Final: `"ORDER-22345-67ABC"`
34+
- Template: `"SO-{0}-2025"` + Generated: `"12345"` = Final: `"SO-12345-2025"`
35+
36+
This approach works with any generator and requires no custom code.
37+
38+
### 2. Using CompactSortableOrderNumberGenerator
39+
40+
The `CompactSortableOrderNumberGenerator` handles most common requirements:
41+
- Compact format (10-11 characters)
42+
- Time-sortable
43+
- Multi-node safe
44+
- High-volume capable (1,024 orders/sec per node)
45+
46+
If your store was upgraded from an earlier version and is using the legacy generator, you can explicitly switch to the recommended generator:
47+
48+
```csharp
49+
public class MyComposer : IComposer
50+
{
51+
public void Compose(IUmbracoBuilder builder)
52+
{
53+
builder.Services.AddUnique<IOrderNumberGenerator, CompactSortableOrderNumberGenerator>();
54+
}
55+
}
56+
```
857

958
## Implementing a Custom Order Number Generator
1059

11-
To create a custom order number generator, define a class that implements the `IOrderNumberGenerator` interface, for example, `CustomOrderNumberGenerator.cs`:
60+
If the built-in generators don't meet your needs, you can create a custom implementation by implementing the `IOrderNumberGenerator` interface.
61+
62+
### Creating the Custom Generator
63+
64+
Define a class that implements the `IOrderNumberGenerator` interface:
1265

1366
{% code title="CustomOrderNumberGenerator.cs" %}
1467

1568
```csharp
69+
using System;
70+
using System.Threading;
71+
using System.Threading.Tasks;
1672
using Umbraco.Commerce.Core.Generators;
73+
using Umbraco.Commerce.Core.Services;
1774

1875
public class CustomOrderNumberGenerator : IOrderNumberGenerator
1976
{
20-
public string GenerateCartNumber(Guid storeId)
77+
private readonly IStoreService _storeService;
78+
79+
public CustomOrderNumberGenerator(IStoreService storeService)
2180
{
22-
// Implement custom logic for cart numbers
81+
_storeService = storeService;
2382
}
2483

25-
public string GenerateOrderNumber(Guid storeId)
84+
public async Task<string> GenerateCartNumberAsync(Guid storeId, CancellationToken cancellationToken = default)
2685
{
27-
// Implement custom logic for order numbers
86+
var store = await _storeService.GetStoreAsync(storeId);
87+
88+
// Implement your custom logic for cart numbers
89+
var cartNumber = $"{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
90+
91+
// Apply store template if configured
92+
return string.Format(store.CartNumberTemplate ?? "{0}", cartNumber);
93+
}
94+
95+
public async Task<string> GenerateOrderNumberAsync(Guid storeId, CancellationToken cancellationToken = default)
96+
{
97+
var store = await _storeService.GetStoreAsync(storeId);
98+
99+
// Implement your custom logic for order numbers
100+
var orderNumber = $"{DateTime.UtcNow:yyyyMMdd}-{Random.Shared.Next(1000, 9999)}";
101+
102+
// Apply store template if configured
103+
return string.Format(store.OrderNumberTemplate ?? "{0}", orderNumber);
28104
}
29105
}
30106
```
31107

32108
{% endcode %}
33109

34-
## Registering the Custom Implementation
110+
### Registering the Custom Implementation
35111

36112
After creating your custom generator, register it in `Program.cs` to replace the default implementation:
37113

38114
{% code title="Program.cs" %}
39115

40116
```csharp
41-
builder.Services.AddUnique<IOrderNumberGenerator, MyOrderNumberGenerator>();
117+
builder.Services.AddUnique<IOrderNumberGenerator, CustomOrderNumberGenerator>();
42118
```
43119

44120
{% endcode %}
45121

46-
The `AddUnique` method ensures that your custom generator replaces the default `IOrderNumberGenerator`. For more details on dependency injection, see the [Dependency Injection](dependency-injection.md) article.
122+
The `AddUnique` method ensures that your custom generator replaces the default `IOrderNumberGenerator`, overriding both the automatic selection system and the built-in generators. For more details on dependency injection, see the [Dependency Injection](dependency-injection.md) article.
47123

48124
## Important Considerations
49125

@@ -54,3 +130,8 @@ Before implementing a custom order number generator, be aware of the following:
54130
- **Accounting Considerations:** Umbraco Commerce is not designed as an accounting platform. If strict sequential numbering is required for accounting purposes, it is recommended to integrate with a dedicated accounting system to handle such requirements.
55131

56132
By understanding these factors, you can implement a custom order number generator that aligns with your specific requirements while maintaining optimal performance and compliance.
133+
134+
## Related Documentation
135+
136+
- [Order Number Generators](../key-concepts/order-number-generators.md) - Detailed documentation about the built-in generators
137+
- [Dependency Injection](dependency-injection.md) - Learn more about registering services in Umbraco Commerce
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
title: Order Number Generators
3+
description: Learn about the `IOrderNumberGenerator` interface in Umbraco Commerce
4+
---
5+
6+
# Order Number Generators
7+
8+
Order Number Generators are responsible for generating unique, human-readable identifiers for carts and orders in Umbraco Commerce. These generators ensure that each cart and order receives a distinct number that can be used for tracking, customer communication, and administrative purposes.
9+
10+
## IOrderNumberGenerator Interface
11+
12+
The `IOrderNumberGenerator` interface defines the contract for generating order and cart numbers:
13+
14+
```csharp
15+
public interface IOrderNumberGenerator
16+
{
17+
Task<string> GenerateCartNumberAsync(Guid storeId, CancellationToken cancellationToken = default);
18+
Task<string> GenerateOrderNumberAsync(Guid storeId, CancellationToken cancellationToken = default);
19+
}
20+
```
21+
22+
The interface provides two key methods:
23+
24+
- **GenerateCartNumberAsync** - Generates a unique number for shopping carts (orders that haven't been finalized)
25+
- **GenerateOrderNumberAsync** - Generates a unique number for finalized orders
26+
27+
Both methods are store-scoped, allowing different stores in a multi-tenant environment to have independent number sequences.
28+
29+
## Built-in Generators
30+
31+
Umbraco Commerce includes two built-in order number generators:
32+
33+
### CompactSortableOrderNumberGenerator (Recommended)
34+
35+
The `CompactSortableOrderNumberGenerator` is the recommended generator introduced in Umbraco Commerce 16.4. It produces compact, time-sortable identifiers with high scalability characteristics.
36+
37+
**Format:** 10-character Base32 encoded ID with hyphen formatting
38+
39+
Where:
40+
- `timeComponent` - 7 Base32 characters encoding seconds since January 1, 2025 (supports ~1089 years)
41+
- `nodeId` - 1 Base32 character representing the server/node ID (0-31)
42+
- `sequence` - 2 Base32 characters encoding a per-second sequence number (0-1023)
43+
44+
**Example:** `22345-67ABC` (hyphen inserted at position 5 for readability)
45+
46+
**Characteristics:**
47+
- **Compact** - Only 10 characters (11 with hyphen formatting)
48+
- **Time-sortable** - Lexicographic ordering matches chronological ordering
49+
- **Scalable** - Supports up to 1,024 orders per second per node (gracefully waits for the next second if capacity is exceeded rather than failing)
50+
- **Multi-node safe** - Node ID prevents collisions in clustered environments
51+
- **Visual variety** - Character rotation based on time reduces visual repetition
52+
53+
**Base32 Alphabet:** Uses Crockford-like Base32 (`23456789ABCDEFGHJKLMNPQRSTUVWXYZ`) which excludes ambiguous characters (0, 1, I, O).
54+
55+
**Number Template Support:**
56+
Cart and order numbers can be formatted using the store's `CartNumberTemplate` and `OrderNumberTemplate` settings (defaults: `"CART-{0}"` and `"ORDER-{0}"`).
57+
58+
### DateHashOrderNumberGenerator (Legacy)
59+
60+
The `DateHashOrderNumberGenerator` is the legacy generator from earlier versions of Umbraco Commerce. It creates order numbers based on the current date and time, combined with a random string component.
61+
62+
{% hint style="info" %}
63+
This generator is maintained for backward compatibility with existing stores, but will eventually be deprecated. New implementations should use `CompactSortableOrderNumberGenerator`.
64+
{% endhint %}
65+
66+
**Format:** `{dayCount:00000}-{timeCount:000000}-{randomString}`
67+
68+
Where:
69+
- `dayCount` - Number of days since January 1, 2020 (5 digits)
70+
- `timeCount` - Number of seconds elapsed in the current day (6 digits for carts, 5 for orders)
71+
- `randomString` - 5 random characters from the set `BCDFGHJKLMNPQRSTVWXYZ3456789`
72+
73+
**Example:** `02103-45678-H4K9P`
74+
75+
**Characteristics:**
76+
- Human-readable with embedded date information
77+
- Random component reduces predictability
78+
- No sequential ordering within the same second
79+
- Collision risk increases if multiple orders are placed simultaneously
80+
- Not suitable for high-volume or multi-node scenarios
81+
82+
## How Generators Are Chosen
83+
84+
Since Umbraco Commerce 16.4, the platform automatically selects the appropriate generator based on your store's state:
85+
86+
- **New installations (16.4+)** - Uses `CompactSortableOrderNumberGenerator` for all new stores
87+
- **Upgrades with existing orders** - Continues using `DateHashOrderNumberGenerator` for stores that already have orders, ensuring consistent number formats
88+
- **Upgrades without orders** - Switches to `CompactSortableOrderNumberGenerator` for stores with no existing orders
89+
90+
Prior to version 16.4, all installations used `DateHashOrderNumberGenerator` by default.
91+
92+
This automatic selection ensures backward compatibility while enabling improved functionality for new stores.
93+
94+
## Explicitly Choosing a Generator
95+
96+
If you want to explicitly choose which generator to use, you can override the registration in your application startup.
97+
98+
### Using CompactSortableOrderNumberGenerator (Recommended)
99+
100+
To explicitly use the compact sortable generator (for example, when migrating an existing store to the new format):
101+
102+
```csharp
103+
public class MyComposer : IComposer
104+
{
105+
public void Compose(IUmbracoBuilder builder)
106+
{
107+
builder.Services.AddUnique<IOrderNumberGenerator, CompactSortableOrderNumberGenerator>();
108+
}
109+
}
110+
```
111+
112+
### Using DateHashOrderNumberGenerator (Legacy Only)
113+
114+
Only use this if you need to maintain the legacy format on a system that was upgraded:
115+
116+
```csharp
117+
public class MyComposer : IComposer
118+
{
119+
public void Compose(IUmbracoBuilder builder)
120+
{
121+
builder.Services.AddUnique<IOrderNumberGenerator, DateHashOrderNumberGenerator>();
122+
}
123+
}
124+
```
125+
126+
{% hint style="warning" %}
127+
Changing the order number generator on an existing store will result in different number formats for new orders. Ensure your business processes and integrations can handle mixed formats before making changes.
128+
{% endhint %}
129+
130+
## Creating a Custom Generator
131+
132+
You can create custom order number generators by implementing the `IOrderNumberGenerator` interface:
133+
134+
```csharp
135+
public class CustomOrderNumberGenerator : IOrderNumberGenerator
136+
{
137+
private readonly IStoreService _storeService;
138+
139+
public CustomOrderNumberGenerator(IStoreService storeService)
140+
{
141+
_storeService = storeService;
142+
}
143+
144+
public async Task<string> GenerateCartNumberAsync(Guid storeId, CancellationToken cancellationToken = default)
145+
{
146+
var store = await _storeService.GetStoreAsync(storeId);
147+
148+
// Your custom cart number generation logic
149+
var cartNumber = Guid.NewGuid().ToString("N")[..8].ToUpperInvariant();
150+
151+
// Apply store template if configured
152+
return string.Format(store.CartNumberTemplate ?? "{0}", cartNumber);
153+
}
154+
155+
public async Task<string> GenerateOrderNumberAsync(Guid storeId, CancellationToken cancellationToken = default)
156+
{
157+
var store = await _storeService.GetStoreAsync(storeId);
158+
159+
// Your custom order number generation logic
160+
var orderNumber = DateTime.UtcNow.ToString("yyyyMMdd") + "-" +
161+
Random.Shared.Next(1000, 9999);
162+
163+
// Apply store template if configured
164+
return string.Format(store.OrderNumberTemplate ?? "{0}", orderNumber);
165+
}
166+
}
167+
```
168+
169+
### Registering a Custom Generator
170+
171+
Register your custom generator during application startup to replace the default implementation:
172+
173+
```csharp
174+
public class MyComposer : IComposer
175+
{
176+
public void Compose(IUmbracoBuilder builder)
177+
{
178+
builder.Services.AddUnique<IOrderNumberGenerator, CustomOrderNumberGenerator>();
179+
}
180+
}
181+
```
182+
183+
### Important Considerations for Custom Generators
184+
185+
When implementing a custom generator, ensure:
186+
187+
1. **Uniqueness** - Generated numbers must be globally unique across all time
188+
2. **Consistency** - The same store should produce numbers in a consistent format
189+
3. **Thread-safety** - Handle concurrent calls safely, especially for sequential numbering
190+
4. **Template support** - Apply the store's `CartNumberTemplate` and `OrderNumberTemplate` settings
191+
5. **Store isolation** - Consider supporting store-specific sequences in multi-store scenarios
192+
6. **Scalability** - Handle high-volume scenarios if your store expects significant traffic
193+
7. **Cluster-awareness** - In multi-node environments, ensure numbers don't collide across nodes
194+
8. **Readability** - Balance uniqueness with human readability for customer communication
195+
196+
{% hint style="warning" %}
197+
Order numbers may have gaps if customers cancel or modify orders during the checkout process. Umbraco Commerce is not designed as an accounting platform. If you require strict sequential numbering for accounting purposes, integration with a dedicated accounting system is recommended. Additionally, sequential numbering can impact performance due to potential database access requirements.
198+
{% endhint %}
199+
200+
## Configuration
201+
202+
Order number templates are configured at the store level and provide a way to add prefixes or suffixes to generated numbers:
203+
204+
```csharp
205+
// Set via the Store entity
206+
await store.SetCartNumberTemplateAsync("CART-{0}");
207+
await store.SetOrderNumberTemplateAsync("ORDER-{0}");
208+
```
209+
210+
The `{0}` placeholder is replaced with the generated number from the active generator.
211+
212+
**Examples:**
213+
- Template: `"ORDER-{0}"` + Generated: `"22345-67ABC"` = Final: `"ORDER-22345-67ABC"`
214+
- Template: `"{0}"` + Generated: `"02103-45678-H4K9P"` = Final: `"02103-45678-H4K9P"`
215+
- Template: `"SO-{0}-2025"` + Generated: `"12345"` = Final: `"SO-12345-2025"`
216+
217+
Templates are applied regardless of which generator is active, providing consistent branding across your order numbers.

0 commit comments

Comments
 (0)