Missing default merging behaviour in Commerce 14

In Commerce 13 and below, we had out of the box default behaviour that when a customer logs in, their cart, wishlist and any orders they made during their anonymous state will get merged into their customer contact associated with their principal object. This is done via a httpmodule where an authentication event would be raised and the IProfileMigrator would get called executing the said actions.

Unfortunately in the current version of Commerce 14 (v14.5.0 at the time of this post) does not include the middleware to hook up the IProfileMigrator and do this automatically as it did with its previous versions.

Luckily if you reading this, you're most likely just looking to provide a solution to your client's requirements. Here it is:

Middleware to hook up IProfileMigrator

public async Task InvokeAsync(HttpContext context)
{
	if (context.User.Identity.IsAuthenticated && !string.IsNullOrEmpty(context.User.Identity?.Name))
	{
		var anonymousId = context.Request.HttpContext.Features.Get()?.AnonymousId;

		if (!string.IsNullOrWhiteSpace(anonymousId))
		{
			var orderRepository = ServiceLocator.Current.GetInstance();
			var currentMarket = ServiceLocator.Current.GetInstance();
			var anonCart = orderRepository.LoadCart(new Guid(anonymousId), "Default", currentMarket);
			if(anonCart != null && anonCart.GetAllLineItems().ToList().Count > 0)
			{
				var profileMigrator = ServiceLocator.Current.GetInstance();
				profileMigrator.MigrateCarts(new Guid(anonymousId));
			}
		}
	}

	await _next(context);
}

Challenges getting to this middleware explained

In Commerce 13 and below, IProfileMigrator was called from an authentication event called MigrateAnonymous attached in httpmodule. Aspnetcore 5 and above doesn't have httpmodules and there doesn't seem to be any appropriate migration event I can take advantage of at the time of writing this post. There are events associated with cookie authentication such as SigningIn, SignedIn and ValidatePrincple; but occur too early in the pipeline for the user object to be instantiated and the correct Customer Contact to be loaded. The same also goes for IAuthorizationMiddlewareResultHandler as implementing this interface allows you check the authorization result and can be useful to target a challenge response but not so useful for targeting a successful authentication.

As a result of this, we have to do additional checks against the state of the anonymous cart and the user principal object to make sure the migrate code is only run once, after the customer logs in.

  1. User.Identity isn't null
  2. Name in User.Identity isn't null or empty
  3. User is Authenticated
  4. Anonymous ID isn't null
  5. Anonymous cart isn't null
  6. Anonymous cart has at least one line item

These guards should be enough to ensure the MigrateCarts called is called and not called again on the next subsequent request.

The example above only shows MigrateCarts but you can also add in MigrateOrders and MigrateWishlists depending on your client requirements.

With the middleware example above. We are attempting to only run the code when the user is authenticated. So within the pipeline, the middleware should be called after the UseAuthentication is called.

 app.UseAuthentication();
 app.UseAuthorization();
 app.EnableAnonymousCartMerging();