A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Sunil
Dec 22, 2025
  104
(1 votes)

Handling Multiple Carts in a Single Sales Agent Session in Optimizely Commerce

In assisted-commerce, call-center, and B2B scenarios, a Sales Agent often needs to work on behalf of multiple customers within a single authenticated session.

A typical requirement looks like this:

  • A Sales Agent logs in once (via OKTA)

  • Uses Select Customer to choose a customer

  • Manages a cart for that customer

  • Switches to another customer

Returns to the previous customer and continues where they left off.

Out of the box, Optimizely Commerce assumes one active cart per user, which makes this scenario non-trivial.

This blog explains a clean, scalable pattern for supporting multiple customer carts within a single Sales Agent session in Optimizely Commerce 14.

Challenges

By default, a cart in Optimizely Commerce is associated with:

  • CustomerId

  • Market

  • Cart name

In assisted-sales scenarios:

  • The logged-in user is the Sales Agent

  • The actual shopper is the selected customer

  • The Sales Agent must manage multiple carts, one per customer

 

Design Principle (which I used)

Treat the Sales Agent as an operator, not the cart owner.

  • Sales Agent identity remains constant

  • Each customer has their own persisted cart

  • Customer context switching is explicit

  • Only one cart is active in the UI at a time

 

 

So to achive this requirement I created a Endpoint which will accepts customer Id's for Active Customer & Selected Customer, so based on the customer Id's system will switch/choose the correct cart and make that cart as primary cart.

 

Input Model for Customer Context Switching

Customer switching is driven by a simple and explicit input model:

public class CustomerCartDetailsInputModel
{
    [DataMember(Name = "activeCustomerId")]
    public string ActiveCustomerId { get; set; }

    [DataMember(Name = "selectedCustomerId")]
    public string SelectedCustomerId { get; set; }
}

Purpose:

  • ActiveCustomerId → customer whose cart is currently active

  • SelectedCustomerId → customer chosen via Select Customer

This avoids implicit session behavior and keeps cart ownership clear.

 

Customer Cart Switching Endpoint

Whenever a Sales Agent clicks Select Customer, the frontend calls a single endpoint:

[HttpPost]
public async Task<JsonResult> Post(
    [FromBody] CustomerCartDetailsInputModel inputModel)
{
    return new JsonHttpStatusResult<CartGetViewModel>(
        await _customerCartService.UpdateCustomerCartAsync(inputModel),
        SerializerSettings.JsonDefault);
}

This endpoint acts as the single source of truth for:

  • Persisting the active customer’s cart

  • Loading or creating the selected customer’s cart

  • Switching the active cart context

 

High-Level Switching Flow 

  1. Resolve Sales Agent identity

  2. Load or create the Sales Agent context cart

  3. Persist the active customer’s cart

  4. Load or create the selected customer’s cart

  5. Copy the selected customer cart into the Sales Agent context

  6. Mark the selected customer as active

 

Simplified Cart Switching Logic (Core Pattern)

Below is a cleaned-up version of the core logic, focused only on multi-cart handling:

public async Task<CartGetViewModel> UpdateCustomerCartAsync(
    CustomerCartDetailsInputModel inputModel)
{
    var salesAgentId = GetSalesAgentId();

    // Load or create Sales Agent context cart
    var agentCart = _orderRepository.LoadOrCreateCart<ICart>(
        salesAgentId, OrderNames.Default);

    // Ensure identifiers exist
    if (string.IsNullOrEmpty(inputModel.ActiveCustomerId))
        inputModel.ActiveCustomerId = Guid.NewGuid().ToString();

    if (string.IsNullOrEmpty(inputModel.SelectedCustomerId))
        inputModel.SelectedCustomerId = Guid.NewGuid().ToString();

    Guid.TryParse(inputModel.ActiveCustomerId, out var activeCustomerId);
    Guid.TryParse(inputModel.SelectedCustomerId, out var selectedCustomerId);

    // Persist active customer cart
    if (activeCustomerId != Guid.Empty)
    {
        var activeCustomerCart =
            _orderRepository.LoadCart<ICart>(activeCustomerId, OrderNames.Default)
            ?? _orderRepository.Create<ICart>(activeCustomerId, OrderNames.Default);

        activeCustomerCart.CopyFrom(agentCart, _orderGroupFactory);
        activeCustomerCart.CustomerId = activeCustomerId;

        _cartRepository.SaveCart(activeCustomerCart);
    }

    // Load or create selected customer cart
    var selectedCustomerCart =
        _orderRepository.LoadCart<ICart>(selectedCustomerId, OrderNames.Default)
        ?? _orderRepository.Create<ICart>(selectedCustomerId, OrderNames.Default);

    selectedCustomerCart.CustomerId = selectedCustomerId;

    // Switch active context
    agentCart.CopyFrom(selectedCustomerCart, _orderGroupFactory);
    agentCart.CustomerId = salesAgentId;

    _cartRepository.SaveCart(agentCart); // Save Cart

    return new CartGetViewModel
    {
        Cart = await BuildCartViewModel(agentCart), // Generate View Model of Cart
        CartCustomerId = selectedCustomerId.ToString()
    };
}

Understanding the CopyFrom Method

CopyFrom is a core Optimizely Commerce API used to deep-copy an entire cart structure from one order group to another.

targetCart.CopyFrom(sourceCart, _orderGroupFactory);

What CopyFrom Does

  • Copies line items, shipments, payments, and addresses

  • Copies custom properties

  • Recreates all entities using IOrderGroupFactory

 

Why CopyFrom Is Essential Here

In multi-cart scenarios:

  • Customer carts must remain isolated

  • The Sales Agent context cart must fully switch state

  • Partial updates are unsafe

CopyFrom guarantees a clean cart switch without data leakage.

 

By separating Sales Agent identity from customer cart ownership and using an explicit Select Customer flow, you can build a scalable assisted-commerce experience on Optimizely Commerce  without fighting the platform.

Below is the output of design:
 

 

Dec 22, 2025

Comments

Please login to comment.
Latest blogs
Optimizely : Show Personalized Content Blocks

I have been exploring Personalization on Optimizely CMS 12 – Simple to set up, and can be utilized throughout the website. I came across this... Th...

Madhu | Dec 23, 2025 |

Troubleshooting with Azure Application Insights Using KQL

Users at least get access to Azure Application Insights even within minimum access level if you are requesting access to DXP management portals at...

K Khan | Dec 21, 2025

Looking back at Optimizely in 2025

Explore Optimizely's architectural shift in 2025, which removed coordination cost through a unified execution loop. Learn how agentic Opal AI and...

Andy Blyth | Dec 17, 2025 |