Sitecore Commerce Connect – Teil 5: Warenkorb verwalten

Bisher ging es in dieser Serie um die Verwaltung und Darstellung von Produkten und Preisen aus Hybris. Das Ziel dieses Artikels ist es auch erste Prozesse rund um den Warenkorb mittels Sitecore Commerce Connect abbilden zu können.

Warenkorb

Die zentrale Überlegung am Anfang eines Projektes ist, welches System zu welcher Zeit die Zuständigkeit für den Warenkorb hat. Wird der Warenkorb immer auf beiden Seiten synchron gehalten, das heißt jede Änderung am Warenkorb wird unmittelbar an das externe Shopsystem (kurz ECS) gemeldet, oder wird der Warenkorb im ECS nur zu definierten Zeitpunkten synchronisiert, also z.B. beim Checkout? Letzteres bedingt weniger Requests an das ECS, aber Sitecore-seitig ist dann z.B. eine Warenkorbwertermittlung erforderlich, die im schlechtesten Fall nicht mit der im ECS synchron ist.

Warenkorb-Verwaltung

Im Rahmen unserer Evaluierung ist das ECS für den Warenkorb zuständig und Sitecore meldet alle Änderungen unmittelbar an das ECS. Für die Darstellung auf der Webseite werden die Daten rund um den Warenkorb wiederum immer online vom ECS abgefragt.
Außerdem haben wir uns entschieden, den mitgelieferten Engagement Plan ‘Abandoned Carts’ gleich mit zu nutzen, zumal dafür scheinbar keine weitere Implementierung erforderlich ist.

Warenkorb anlegen/ abfragen

Für alle Funktionen rund um den Warenkorb ist der Service Provider ‚CartServiceProvider‘ des Cart Service Layers zuständig.

Beispiel Verwendung CartServiceProvider.CreateOrResumeCart

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
	/// <summary>
	/// Sitecore.Commerce.Carts.config
	/// </summary>
	public class CartManager
	{
		public const string TrackerAttachmentKeyCart = "Comspace.Sitecore.CommerceConnect_Cart";

		public static Cart GetCart()
		{
			if (!Tracker.Current.Contact.Attachments.ContainsKey(TrackerAttachmentKeyCart))
			{
				//create cart
				var createOrResumeCartRequest = new CreateOrResumeCartRequest(
					"Website",
					Context.User.Profile.Email,
					"Default",
					Context.User.Profile.Email); //NOTE FindCartInEaState: c.CustomerId == request.CustomerId
				var cart = new CartServiceProvider().CreateOrResumeCart(createOrResumeCartRequest).Cart;

				//persist cart
				Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] = cart;
			}
			return Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] as Cart;
		}
	}

	//...
}

Pipeline loadCart

Die Anbindung des ECS erfolgt mittels Erweiterung der Pipeline ‚loadCart‘ der Sitecore.Commerce.Carts.config.
.config

<!-- LOAD CART
	Gets the cart object that matches the specified criteria.
	This pipeline is responsible for reading data for a specific cart that is managed by the commerce system.
	This pipeline reads the cart data from the commerce system and converts that data into the Commerce format.
-->
<commerce.carts.loadCart>
	<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts.LoadCart, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type='Sitecore.Commerce.Pipelines.Carts.LoadCart.LoadCartFromEaState, Sitecore.Commerce']"/>
</commerce.carts.loadCart>

Processor LoadCart

using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model.Cart;
using Comspace.Sitecore.CommerceConnect.Services.Carts;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Pipelines.Carts.Common;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts
{
	/// <summary>
	/// commerce.carts.loadCart
	/// </summary>
	public class LoadCart : CartPipelineProcessor
	{
		public override void Process(ServicePipelineArgs args)
		{
			var loadCartRequest = args.Request as LoadCartRequest;

			//get from ECS
			ExternalCart externalCart = CartConnector.Load(global::Sitecore.Context.Language.Name, loadCartRequest.UserId);

			//convert to commerce connect cart
			Cart cart = new Cart();
			cart.ExternalId = externalCart.Code;
			cart.ShopName = loadCartRequest.ShopName;
			cart.UserId = loadCartRequest.UserId;

			//init totals
			cart = CartManager.UpdateCartTotals(cart); //NOTE prevent multiple request

			//persist for next processor
			((CartResult) args.Result).Cart = cart;
		}
	}
}

Produkt zum Warenkorb hinzufügen

Beispiel Verwendung CartServiceProvider.AddCartLines

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
	/// <summary>
	/// Sitecore.Commerce.Carts.config
	/// </summary>
	public class CartManager
	{
		//...

		public static Cart AddLine(string externalId, string sitecoreItemId, uint quantitiy)
		{
			Cart cart = GetCart();

			//create cartlines
			List<CartLine> lines = new List<CartLine>
			 {
				 new CartLine
				 {
					 Product = new CartProduct
					 {
						 ProductId = externalId,
						 SitecoreProductItemId = new ID(sitecoreItemId),
						 Price = PriceManager.GetPrice(externalId, Context.Language)
					 },
					 Quantity = quantitiy,
					 LineNumber = (uint) (cart.Lines.Count + 1)
				 }
			 };

			//add cart lines
			AddCartLinesRequest addCartLinerequest = new AddCartLinesRequest(cart, new ReadOnlyCollection<CartLine>(lines));
			CartResult cartResult = new CartServiceProvider().AddCartLines(addCartLinerequest);

			return cartResult.Cart; //result contains the updated cart
		}
	}
}

Pipeline addCartLines

.config

<!-- ADD CART LINES
	This pipeline is responsible for adding a new line to the shopping cart and recording a corresponding page event in DMS.
	This happens when a product is added to the cart.
-->
<commerce.carts.addCartLines>
	<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts.AddLineToCart, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type='Sitecore.Commerce.Pipelines.Carts.AddCartLines.AddLinesToCart, Sitecore.Commerce']"/>
</commerce.carts.addCartLines>

Processor AddCartLines

using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts
{
	/// <summary>
	/// commerce.carts.addCartLines
	/// </summary>
	public class AddLineToCart : PipelineProcessor<ServicePipelineArgs>
	{
		public override void Process(ServicePipelineArgs args)
		{
			var cartLinesRequest = args.Request as CartLinesRequest;

			//add to ECS
			Cart cart = cartLinesRequest.Cart;
			CartLine cartline = cartLinesRequest.Lines.First();
			CartConnector.AddLineToCard(global::Sitecore.Context.Language.Name, cart, cartline);
		}
	}
}

Warenkorb Summen

Damit die Summen im Warenkorb immer korrekt sind und keine Sitecore-seitige Berechnung stattfinden muss, werden auch die Warenkorb Summen immer online aus dem ECS geholt. Dafür muss die Pipeline ‘saveCart’ erweitert werden:

Pipeline saveCart

.config

<!-- SAVE CART
	Saves the cart object to the external system and in Sitecore EA state.
-->
<commerce.carts.saveCart>
	<processor type="Comspace.Sitecore.CommerceConnect.Pipelines.Carts.UpdateCartTotals, Comspace.Sitecore.CommerceConnect" patch:after="processor[@type='Sitecore.Commerce.Pipelines.Carts.Common.SaveCartToEaState, Sitecore.Commerce']"/>
</commerce.carts.saveCart>

Processor UpdateCartTotals

using Comspace.Sitecore.CommerceConnect.Services.Carts;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Pipelines.Carts
{
	/// <summary>
	/// commerce.carts.saveCart
	/// </summary>
	public class UpdateCartTotals : PipelineProcessor<ServicePipelineArgs>
	{
		public override void Process(ServicePipelineArgs args)
		{
			var cartRequest = (CartRequestWithCart) args.Request;

			//get from ECS
			Cart cart = CartManager.UpdateCartTotals(cartRequest.Cart);

			//persist for next processor
			((CartResult) args.Result).Cart = cart;
		}
	}
}

Beispiel Verwendung PricingServiceProvider.GetCartTotal

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
	/// <summary>
	/// Sitecore.Commerce.Carts.config
	/// </summary>
	public class CartManager
	{
		//...

		public static Cart UpdateCartTotals(Cart cart)
		{
			var request = new GetCartTotalRequest();
			request.Cart = cart;

			//get from ECS
			var cartTotalResult = new PricingServiceProvider().GetCartTotal(request); //result contains the updated cart

			Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] = cartTotalResult.Cart; //refresh cache
			return cartTotalResult.Cart;
		}
	}
}

Stolpersteine

Der Processor ‘FindCartInEaState’ benötigt eine CustomerId, damit der Contact ermittelt werden kann. Dafür muss die eigentlich optionale CustomerId mit an den ‘CreateOrResumeCartRequest’ übergeben werden (siehe oben). Anderenfalls wird immer wieder ein neuer Warenkorb angelegt, obwohl für den Contact bereits einer existiert.

Resumé

Und auch diesen letzten Beitrag möchte ich mit ein paar Eindrücken und Erkenntnissen abschließen:

  • Der Cart Sercice Layer ist sehr umfangreich und im Rahmen der Evaluierung konnte nur ein sehr kleiner Ausschnitt evaluiert werden.
  • Eine Herausforderung in einem echten Projekt sind die Zuständigkeit und Synchronisation zwischen dem Sitecore Shop und dem ECS. Was wird wo zwischengespeichert und wann an wen gemeldet?
  • Wie auch schon unter Stolpersteine beschrieben, war der Zugriff auf die Contacts im Engangement Plan (automatisch im Hintergrund) immer wieder holprig. Eine der Ursachen war sicherlich die Entwicklungsumgebung: wechselnde Browser, plötzliche Neustarts etc.  Hier müsste in einem echten Projekt eine kritische Analyse erfolgen.
  • Auch dem Umgang mit Fehlern in der Verarbeitung muss in einem echten Projekt eine hohe Aufmerksamkeit gewidmet werden – Stichwort Transaktionssicherheit.

So, das war’s erstmal von meiner Seite zum Thema Commerce Connect. Ich freue mich über euer Feedback :)

Alle voran gegangenen Artikel dieser Serie findet ihr hier:
Teil 1: Ein Überblick
Teil 2: Produkte synchronisieren
Teil 3: Klassifizierungen synchronisieren
Teil 4: Preise darstellen

Über Friederike Heinze

Friederike Heinze arbeitet im Team Solution Architecture bei comspace. Im Projekt ist sie für eine Überführung von Anforderungen in technische Lösungen verantwortlich. Als Sitecore Expertin beobachtet sie Neuentwicklungen und Veränderungen auch über ein Projekt hinaus.
Artikel teilen:

Verwandte Themen

Trackbacks/ Pingbacks

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.