Surjit Bharath
Aug 4, 2022
visibility 3315
star star star star star
(2 votes)

Merging carts when customer logs in on 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.

For the example middleware code please go here: https://www.hiddenfoundry.com/thoughts/merging-carts-when-logging-in-from-an-anonymous-state-in-commerce-14/

Aug 04, 2022

Comments

Johannes Mols
Johannes Mols Jun 6, 2023 09:32 AM

Hi, thank you for the blog post, it helped a lot. The code snippet isn't fully working though regarding the service locator and getting the anonymous ID as a feature. Not sure if that's due to the updated in Commerce (running v14.11.0) or because it's incomplete.

For me, the anonymous ID is also removed as soon as the user is authenticated. A workaround I found is to get the ID from the cookie that the anonymous ID middleware sets. This isn't deleted upon logging in.

Here is the middleware that is working for me:

public class AnonymousCartMigrationMiddleware
{
	private readonly RequestDelegate _next;
	private readonly IOrderRepository _orderRepository;
	private readonly ICurrentMarket _currentMarket;
	private readonly IProfileMigrator _profileMigrator;

	public AnonymousCartMigrationMiddleware(
		RequestDelegate next,
		IOrderRepository orderRepository,
		ICurrentMarket currentMarket,
		IProfileMigrator profileMigrator)
	{
		_next = next;
		_orderRepository = orderRepository;
		_currentMarket = currentMarket;
		_profileMigrator = profileMigrator;
	}

	public async Task Invoke(HttpContext context)
	{
		if (context.User.Identity is { IsAuthenticated: true } && !string.IsNullOrEmpty(context.User.Identity?.Name))
		{
			var justLoggedIn = CustomContext.Current.IsFirstLoadAfterLogin;

			// Not using "context.Request.HttpContext.Features.Get<IAnonymousIdFeature>()?.AnonymousId" because it is removed at this point. The cookie, however, is not deleted (see AnonymousIdMiddleware).
			context.Request.Cookies.TryGetValue("EPiServer_Commerce_AnonymousId", out var anonymousId);

			if (justLoggedIn && !string.IsNullOrWhiteSpace(anonymousId) && Guid.TryParse(anonymousId, out var anonymousGuid))
			{
				var cart = _orderRepository.LoadCart<ICart>(anonymousGuid, "Default", _currentMarket);
				if (cart != null && cart.GetAllLineItems().Any())
				{
					_profileMigrator.MigrateCarts(anonymousGuid);
					_profileMigrator.MigrateOrders(anonymousGuid);
					_profileMigrator.MigrateWishlists(anonymousGuid);
				}

				// Add it back in so that the application can process it as normal.
				context.Session.SetString(SessionKeys.JustLoggedIn, "true");
			}
		}

		await _next(context);
	}
}

public static class AnonymousCartMigrationMiddlewareExtensions
{
	public static IApplicationBuilder UseAnonymousCartMigration(this IApplicationBuilder app)
	{
		return app.UseMiddleware<AnonymousCartMigrationMiddleware>();
	}
}

The CustomContext is an implementation that handles session info. When we process a login, we store a value in the user session that flags it as the first load after log-in. This is used elsewhere in the application. Accessing this property removes the session key, which is why I add it back in after running the migration. This prevents multiple requests from triggering the migration simulatenously.

Hope this helps someone.

Surjit Bharath
Surjit Bharath Jun 6, 2023 04:14 PM

Thanks for the comment. Sorry the initial attempt didn't work. I've just tried it on the latest version of Commerce 14.12 on a fresh Foundation instance and it was working just fine.

Just make sure you call your middleware after:

app.AnonymousId();

app.UseAuthentication();

app.UseAuthorization();

Johannes Mols
Johannes Mols Jun 9, 2023 06:18 AM

Hi, yes it seems I had the UseAnonymousId middleware after the two. Moving it to the front makes it work :) Maybe you'll want to add that line to your blog post in the last code snippet.

The syntax to get the Id seems to also have changed slightly from your snippet. This works: 

context.Request.HttpContext.Features.Get<IAnonymousIdFeature>()?.AnonymousId

Praful Jangid
Praful Jangid Oct 3, 2024 03:42 PM

Surjit Bharath,

You deserve 5 star for this post. Thanks for saving our life.

error Please login to comment.
Latest blogs
Implementing the Bynder DAM Connector with Optimizely SaaS CMS: Lessons Learned

What I learned while integrating Bynder DAM with Optimizely SaaS CMS, exploring Optimizely Graph, and building a headless frontend experience....

Vipin Banka | Jul 3, 2026

Optimizely London developer meetup 2026: a round up

Well, what can I say? Last night we wrapped up! Yet another London Developer Meetup, hosted at the superb Lightwell venue And this is also a...

Scott Reed | Jul 3, 2026

AvantiBit Custom Settings for Optimizely CMS

AvantiBit Custom Settings is a free, Apache-2.0 Optimizely CMS add-on for typed, site- and language-aware configuration that stays out of content...

Enes Bajramovic | Jul 3, 2026 |

Building an experience with Visual Builder in Optimizely CMS 13

Visual Builder changes how we can think about campaign pages, landing pages and other highly curated editorial experiences in Optimizely CMS. Inste...

Pär Wissmark | Jul 2, 2026 |

LanguageMaster! From Managing to Mastering Languages!

Two years ago, I released my first Optimizely add-on . It was an extension to the Labs.LanguageManager tool from Optimizely that allowed the user t...

Matt Pallatt | Jul 2, 2026

List Properties of a Optimizely Content Type programmatically

Properties are simply fields used to create a content type in Optimizely. Lets explore how to get a list of properties of a specific content type...

Akash Borkar | Jul 2, 2026