When a user signs in, you must make sure that any anonymous Cart they were using is correctly managed. A seamless transition of the Cart's contents is critical for user experience. This section details the two primary strategies for managing Cart data upon sign-in: using the built-in
anonymousCartSignInMode
for automatic merging and implementing custom merge logic for more complex scenarios.Use anonymousCartSignInMode
The
CustomerSignIn
and MyCustomerSignIn
types provide the anonymousCartSignInMode
field to control how an anonymous Cart is handled when its owner signs in. This is the most direct method to manage Cart transitions.The available modes are as follows:
MergeWithExistingCustomerCart
: merges the Line Items of an anonymous Cart with the Customer's existing active Cart. If the Customer has no active Cart, then the anonymous Cart becomes their new active Cart.UseAsNewActiveCustomerCart
: the anonymous Cart becomes the Customer's new active Cart. Any existing active Cart is disassociated from the Customer, but it isn't deleted.
Example - Comparison of Cart merging strategies
This example describes the difference between the
MergeWithExistingCustomerCart
and UseAsNewActiveCustomerCart
modes.Let's assume that the anonymous Cart (
anon-cart-1
) contains one Zen Speaker X item and the Customer's existing active cart (cust-cart-A
) contains one Zen Tablet Y item.import { apiRoot } from '../../client'; // Assuming apiRoot is initialized and exported here
import {
CustomerSignInResult,
AnonymousCartSignInMode,
} from '@commercetools/platform-sdk'; // Import necessary types
async function simulateLoginAndCartHandlingInStore(
storeKey: string,
email: string,
password: string,
anonymousCartId: string,
mode: AnonymousCartSignInMode
): Promise<CustomerSignInResult> {
try {
const requestBody = {
email: email,
password: password,
anonymousCartId: anonymousCartId, // This is always a string as per function signature
anonymousCartSignInMode: mode,
};
console.log(
`\n--- Attempting in-store login for ${email} with anonymous cart ${anonymousCartId} using mode: ${mode} for store "${storeKey}" ---`
);
const loginResponse = await apiRoot
.inStoreKeyWithStoreKeyValue({ storeKey: storeKey })
.login()
.post({
body: requestBody,
})
.execute();
const signInResult: CustomerSignInResult = loginResponse.body; // Explicitly type the response body
console.log(
`In-Store Login successful. Customer: ${signInResult.customer.email}`
);
console.log(`Active Cart ID: ${signInResult.cart?.id}`);
if (signInResult.cart?.lineItems) {
console.log('Cart Line Items:');
signInResult.cart.lineItems.forEach((item) => {
// Ensure name is accessed safely, as it's a LocalizedString
console.log(` - ${item.name['en-US']} (Quantity: ${item.quantity})`);
});
}
return signInResult;
} catch (error: any) {
// Type 'any' for caught error for now because stricter typing for HTTP errors can be complex
console.error(
`In-Store Login failed with mode ${mode} for store "${storeKey}":`,
error
);
throw error;
}
}
(async () => {
const davidEmail = 'david.shopper@example.com';
const davidPassword = 'DavidSecurePass!';
const anonCartId = 'anon-cart-1'; // Represents a Cart with Zen Speaker X (1 unit)
const ELECTRONICS_HIGH_TECH_STORE_KEY = 'electronics-high-tech-store';
// Assume david.shopper@example.com already has a Customer cart 'cust-cart-A' that contains Zen Tablet Y (1 unit)
// and is associated with 'electronics-high-tech-store'
// Scenario 1: MergeWithExistingCustomerCart for 'electronics-high-tech-store'
// Expect: A single active Cart with Zen Speaker X (1 unit) and Zen Tablet Y (1 unit)
try {
await simulateLoginAndCartHandlingInStore(
ELECTRONICS_HIGH_TECH_STORE_KEY,
davidEmail,
davidPassword,
anonCartId,
'MergeWithExistingCustomerCart'
);
} catch (e) {
console.error(
`Failed to simulate MergeWithExistingCustomerCart scenario for store ${ELECTRONICS_HIGH_TECH_STORE_KEY}.`
);
/* handle error */
}
// Scenario 2: UseAsNewActiveCustomerCart for 'electronics-high-tech-store'
// To properly demonstrate this, either create an anonymous Cart for David when he is in guest mode (not logged in) or use a different Customer for a fresh scenario.
// Expect: A single active Cart with ONLY Zen Speaker X (1 unit), and the Cart with Zen Tablet Y (1 unit) is hidden, which means that this Cart isn't returned by the query for the Customer's active Cart.
try {
await simulateLoginAndCartHandlingInStore(
ELECTRONICS_HIGH_TECH_STORE_KEY,
davidEmail,
davidPassword,
anonCartId,
'UseAsNewActiveCustomerCart'
);
} catch (e) {
console.error(
`Failed to simulate UseAsNewActiveCustomerCart scenario for store ${ELECTRONICS_HIGH_TECH_STORE_KEY}.`
);
/* handle error */
}
})();
Implement custom Cart merging logic
Although the built-in
anonymousCartSignInMode
is sufficient for most use cases, you might need to implement custom Cart merging logic for specific business requirements.When to use custom Cart merging logic
You should consider implementing a custom Cart merging logic if you want to:
- Apply complex business rules for Cart merging: for example, when you want to merge only some items from specific categories or you want to add duplicate Products as separate Line Items instead of increasing the quantity.
- Allow Customer selection: present both Carts to a user post-login, and then allow them to manually select the items that they want to keep.
- Merge other data points: manage complex merging of custom fields on the cart or Line Items.
- Have custom conflict resolution: go beyond the default Cart merging behavior to handle conflicts in a specific manner.
How to implement custom Cart merging logic
A custom Cart merging process includes the following steps, which are managed by your application's backend:
-
Sign in a Customer without the
anonymousCartId
: perform aPOST /{projectKey}/login
request with only the user's credentials. This prevents the automatic merging of Carts and gives you thecustomerId
and any existingactiveCart
details. -
Fetch the anonymous Cart: retrieve the anonymous Cart's ID from the client (for example, a cookie), and then fetch its details by using the
GET /{projectKey}/carts/{id}
request. -
Fetch the Customer's active Cart: if the login response didn't include an active Cart, then you can associate the anonymous Cart with the Customer. If an active Cart exists, then fetch its latest version to ensure that you have the most up-to-date data before merging.Fetching a Cart directly doesn't trigger a recalculation. If Prices or Discounts on Products have changed, then you can recalculate the Cart to update it with the latest values.
-
Apply your custom Cart merging logic: iterate through the Line Items and other properties of both Carts. Use Cart update actions to build the final merged Cart. Ensure that all relevant Cart Attributes (such as Discounts, Custom Fields, and Shipping Method) are correctly transferred or combined according to your specific rules. Important update actions include the following:
addLineItem
changeLineItemQuantity
removeLineItem
setCustomField
-
Delete the original anonymous Cart (optional): after its contents are successfully merged, delete the original anonymous Cart by using the
DELETE /{projectKey}/carts/{id}
request to maintain data hygiene.
Implementing custom Cart merging logic offers maximum flexibility; however, it also introduces significant complexity in development, testing, and maintenance.
Key takeaways
- Use the
anonymousCartSignInMode
field on sign-in for standard Cart merging scenarios. MergeWithExistingCustomerCart
combines the anonymous and active Customer Carts.UseAsNewActiveCustomerCart
replaces a Customer's active Cart with their anonymous Cart.- For complex business rules such as conditional item merging or user-driven selection, implement custom logic in your backend.
- A custom implementation involves signing in a user, fetching both their Carts, applying merge rules with update actions, and cleaning up the original anonymous Cart.