flamingo.me/flamingo-commerce/v3@v3.11.0/cart/application/cartService.go (about)

     1  package application
     2  
     3  import (
     4  	"context"
     5  	"encoding/gob"
     6  	"fmt"
     7  
     8  	"go.opencensus.io/trace"
     9  
    10  	"flamingo.me/flamingo/v3/core/auth"
    11  	"github.com/pkg/errors"
    12  
    13  	"flamingo.me/flamingo/v3/framework/flamingo"
    14  	"flamingo.me/flamingo/v3/framework/web"
    15  
    16  	cartDomain "flamingo.me/flamingo-commerce/v3/cart/domain/cart"
    17  	"flamingo.me/flamingo-commerce/v3/cart/domain/decorator"
    18  	"flamingo.me/flamingo-commerce/v3/cart/domain/events"
    19  	"flamingo.me/flamingo-commerce/v3/cart/domain/placeorder"
    20  	"flamingo.me/flamingo-commerce/v3/cart/domain/validation"
    21  	productDomain "flamingo.me/flamingo-commerce/v3/product/domain"
    22  )
    23  
    24  // CartService application struct
    25  type (
    26  	// CartService provides methods to modify the cart
    27  	CartService struct {
    28  		cartReceiverService *CartReceiverService
    29  		webIdentityService  *auth.WebIdentityService
    30  		productService      productDomain.ProductService
    31  		eventPublisher      events.EventPublisher
    32  		eventRouter         flamingo.EventRouter
    33  		deliveryInfoBuilder cartDomain.DeliveryInfoBuilder
    34  		logger              flamingo.Logger
    35  		defaultDeliveryCode string
    36  		restrictionService  *validation.RestrictionService
    37  		deleteEmptyDelivery bool
    38  		// optionals - these may be nil
    39  		cartValidator     validation.Validator
    40  		itemValidator     validation.ItemValidator
    41  		cartCache         CartCache
    42  		placeOrderService placeorder.Service
    43  	}
    44  
    45  	// RestrictionError error enriched with result of restrictions
    46  	RestrictionError struct {
    47  		message           string
    48  		CartItemID        string
    49  		RestrictionResult validation.RestrictionResult
    50  	}
    51  
    52  	// QtyAdjustmentResult restriction result enriched with the respective item
    53  	QtyAdjustmentResult struct {
    54  		OriginalItem          cartDomain.Item
    55  		DeliveryCode          string
    56  		WasDeleted            bool
    57  		RestrictionResult     *validation.RestrictionResult
    58  		NewQty                int
    59  		HasRemovedCouponCodes bool
    60  	}
    61  
    62  	// QtyAdjustmentResults slice of QtyAdjustmentResult
    63  	QtyAdjustmentResults []QtyAdjustmentResult
    64  
    65  	// PromotionFunction type takes ctx, cart, couponCode and applies the promotion
    66  	promotionFunc func(context.Context, *cartDomain.Cart, string) (*cartDomain.Cart, cartDomain.DeferEvents, error)
    67  
    68  	contextKeyType string
    69  )
    70  
    71  const (
    72  	itemIDKey contextKeyType = "item_id"
    73  )
    74  
    75  func ItemIDFromContext(ctx context.Context) string {
    76  	id, _ := ctx.Value(itemIDKey).(string)
    77  
    78  	return id
    79  }
    80  
    81  func ContextWithItemID(ctx context.Context, itemID string) context.Context {
    82  	return context.WithValue(ctx, itemIDKey, itemID)
    83  }
    84  
    85  func init() {
    86  	gob.Register(RestrictionError{})
    87  	gob.Register(QtyAdjustmentResults{})
    88  }
    89  
    90  // Error fetch error message
    91  func (e *RestrictionError) Error() string {
    92  	return e.message
    93  }
    94  
    95  var (
    96  	_                          Service = &CartService{}
    97  	ErrBundleConfigNotProvided         = errors.New("error no bundle config provided")
    98  	ErrProductNotTypeBundle            = errors.New("product not of type bundle")
    99  )
   100  
   101  // Inject dependencies
   102  func (cs *CartService) Inject(
   103  	cartReceiverService *CartReceiverService,
   104  	productService productDomain.ProductService,
   105  	eventPublisher events.EventPublisher,
   106  	eventRouter flamingo.EventRouter,
   107  	deliveryInfoBuilder cartDomain.DeliveryInfoBuilder,
   108  	restrictionService *validation.RestrictionService,
   109  	webIdentityService *auth.WebIdentityService,
   110  	logger flamingo.Logger,
   111  	config *struct {
   112  		DefaultDeliveryCode string `inject:"config:commerce.cart.defaultDeliveryCode,optional"`
   113  		DeleteEmptyDelivery bool   `inject:"config:commerce.cart.deleteEmptyDelivery,optional"`
   114  	},
   115  	optionals *struct {
   116  		CartValidator     validation.Validator     `inject:",optional"`
   117  		ItemValidator     validation.ItemValidator `inject:",optional"`
   118  		CartCache         CartCache                `inject:",optional"`
   119  		PlaceOrderService placeorder.Service       `inject:",optional"`
   120  	},
   121  ) {
   122  	cs.cartReceiverService = cartReceiverService
   123  	cs.productService = productService
   124  	cs.eventPublisher = eventPublisher
   125  	cs.eventRouter = eventRouter
   126  	cs.deliveryInfoBuilder = deliveryInfoBuilder
   127  	cs.restrictionService = restrictionService
   128  	cs.webIdentityService = webIdentityService
   129  	cs.logger = logger.WithField(flamingo.LogKeyModule, "cart").WithField(flamingo.LogKeyCategory, "application.cartService")
   130  	if config != nil {
   131  		cs.defaultDeliveryCode = config.DefaultDeliveryCode
   132  		cs.deleteEmptyDelivery = config.DeleteEmptyDelivery
   133  	}
   134  	if optionals != nil {
   135  		cs.cartValidator = optionals.CartValidator
   136  		cs.itemValidator = optionals.ItemValidator
   137  		cs.cartCache = optionals.CartCache
   138  		cs.placeOrderService = optionals.PlaceOrderService
   139  	}
   140  }
   141  
   142  // GetCartReceiverService returns the injected cart receiver service
   143  func (cs *CartService) GetCartReceiverService() *CartReceiverService {
   144  	return cs.cartReceiverService
   145  }
   146  
   147  // ValidateCart validates a carts content
   148  func (cs *CartService) ValidateCart(ctx context.Context, session *web.Session, decoratedCart *decorator.DecoratedCart) validation.Result {
   149  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ValidateCart")
   150  	defer span.End()
   151  
   152  	if cs.cartValidator != nil {
   153  		result := cs.cartValidator.Validate(ctx, session, decoratedCart)
   154  
   155  		return result
   156  	}
   157  
   158  	return validation.Result{}
   159  }
   160  
   161  // ValidateCurrentCart validates the current active cart
   162  func (cs *CartService) ValidateCurrentCart(ctx context.Context, session *web.Session) (validation.Result, error) {
   163  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ValidateCurrentCart")
   164  	defer span.End()
   165  
   166  	decoratedCart, err := cs.cartReceiverService.ViewDecoratedCart(ctx, session)
   167  	if err != nil {
   168  		return validation.Result{}, err
   169  	}
   170  
   171  	return cs.ValidateCart(ctx, session, decoratedCart), nil
   172  }
   173  
   174  // UpdatePaymentSelection updates the paymentselection in the cart
   175  func (cs *CartService) UpdatePaymentSelection(ctx context.Context, session *web.Session, paymentSelection cartDomain.PaymentSelection) error {
   176  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdatePaymentSelection")
   177  	defer span.End()
   178  
   179  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	// cart cache must be updated - with the current value of cart
   184  	var defers cartDomain.DeferEvents
   185  	defer func() {
   186  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   187  		cs.dispatchAllEvents(ctx, defers)
   188  	}()
   189  
   190  	cart, defers, err = behaviour.UpdatePaymentSelection(ctx, cart, paymentSelection)
   191  	if err != nil {
   192  		cs.handleCartNotFound(session, err)
   193  
   194  		if !errors.Is(err, context.Canceled) {
   195  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdatePaymentSelection").Error(err)
   196  		}
   197  
   198  		return err
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  // UpdateBillingAddress updates the billing address on the cart
   205  func (cs *CartService) UpdateBillingAddress(ctx context.Context, session *web.Session, billingAddress *cartDomain.Address) error {
   206  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateBillingAddress")
   207  	defer span.End()
   208  
   209  	if billingAddress == nil {
   210  		return nil
   211  	}
   212  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	// cart cache must be updated - with the current value of cart
   217  	var defers cartDomain.DeferEvents
   218  	defer func() {
   219  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   220  		cs.dispatchAllEvents(ctx, defers)
   221  	}()
   222  
   223  	cart, defers, err = behaviour.UpdateBillingAddress(ctx, cart, *billingAddress)
   224  	if err != nil {
   225  		cs.handleCartNotFound(session, err)
   226  
   227  		if !errors.Is(err, context.Canceled) {
   228  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateBillingAddress").Error(err)
   229  		}
   230  
   231  		return err
   232  	}
   233  
   234  	return nil
   235  }
   236  
   237  // UpdateDeliveryInfo updates the delivery info on the cart
   238  func (cs *CartService) UpdateDeliveryInfo(ctx context.Context, session *web.Session, deliveryCode string, deliveryInfo cartDomain.DeliveryInfoUpdateCommand) error {
   239  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateDeliveryInfo")
   240  	defer span.End()
   241  
   242  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	// cart cache must be updated - with the current value of cart
   247  	var defers cartDomain.DeferEvents
   248  	defer func() {
   249  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   250  		cs.dispatchAllEvents(ctx, defers)
   251  	}()
   252  
   253  	if deliveryCode == "" {
   254  		deliveryCode = cs.defaultDeliveryCode
   255  	}
   256  
   257  	cart, defers, err = behaviour.UpdateDeliveryInfo(ctx, cart, deliveryCode, deliveryInfo)
   258  	if err != nil {
   259  		cs.handleCartNotFound(session, err)
   260  
   261  		if !errors.Is(err, context.Canceled) {
   262  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateDeliveryInfo").Error(err)
   263  		}
   264  
   265  		return err
   266  	}
   267  
   268  	return nil
   269  }
   270  
   271  // UpdatePurchaser updates the purchaser on the cart
   272  func (cs *CartService) UpdatePurchaser(ctx context.Context, session *web.Session, purchaser *cartDomain.Person, additionalData *cartDomain.AdditionalData) error {
   273  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdatePurchaser")
   274  	defer span.End()
   275  
   276  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   277  	if err != nil {
   278  		return err
   279  	}
   280  	// cart cache must be updated - with the current value of cart
   281  	var defers cartDomain.DeferEvents
   282  	defer func() {
   283  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   284  		cs.dispatchAllEvents(ctx, defers)
   285  	}()
   286  
   287  	cart, defers, err = behaviour.UpdatePurchaser(ctx, cart, purchaser, additionalData)
   288  	if err != nil {
   289  		cs.handleCartNotFound(session, err)
   290  
   291  		if !errors.Is(err, context.Canceled) {
   292  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdatePurchaser").Error(err)
   293  		}
   294  
   295  		return err
   296  	}
   297  
   298  	return nil
   299  }
   300  
   301  // UpdateItemQty updates a single cart item qty
   302  func (cs *CartService) UpdateItemQty(ctx context.Context, session *web.Session, itemID string, deliveryCode string, qty int) error {
   303  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateItemQty")
   304  	defer span.End()
   305  
   306  	if qty < 1 {
   307  		// item needs to be removed, let DeleteItem handle caching/events
   308  		return cs.DeleteItem(ctx, session, itemID, deliveryCode)
   309  	}
   310  
   311  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   312  	if err != nil {
   313  		return err
   314  	}
   315  	// cart cache must be updated - with the current value of cart
   316  	var defers cartDomain.DeferEvents
   317  	defer func() {
   318  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   319  		cs.dispatchAllEvents(ctx, defers)
   320  	}()
   321  
   322  	if deliveryCode == "" {
   323  		deliveryCode = cs.defaultDeliveryCode
   324  	}
   325  
   326  	item, err := cart.GetByItemID(itemID)
   327  	if err != nil {
   328  
   329  		if !errors.Is(err, context.Canceled) {
   330  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemQty").Error(err)
   331  		}
   332  
   333  		return err
   334  	}
   335  
   336  	qtyBefore := item.Qty
   337  
   338  	product, err := cs.productService.Get(ctx, item.MarketplaceCode)
   339  	if err != nil {
   340  		if !errors.Is(err, context.Canceled) {
   341  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemQty").Error(err)
   342  		}
   343  		return err
   344  	}
   345  
   346  	product, err = cs.getSpecificProductType(ctx, product, item.VariantMarketPlaceCode, item.BundleConfig)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	err = cs.checkProductQtyRestrictions(ctx, session, product, cart, qty-qtyBefore, deliveryCode, itemID)
   352  	if err != nil {
   353  		cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemQty").Info(err)
   354  
   355  		return err
   356  	}
   357  
   358  	itemUpdate := cartDomain.ItemUpdateCommand{
   359  		Qty:            &qty,
   360  		ItemID:         itemID,
   361  		AdditionalData: nil,
   362  	}
   363  
   364  	cart, defers, err = behaviour.UpdateItem(ctx, cart, itemUpdate)
   365  	if err != nil {
   366  		cs.handleCartNotFound(session, err)
   367  		if !errors.Is(err, context.Canceled) {
   368  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemQty").Error(err)
   369  		}
   370  		return err
   371  	}
   372  
   373  	// append deferred events of behaviour with changed qty event
   374  	updateEvent := &events.ChangedQtyInCartEvent{
   375  		Cart:                   cart,
   376  		CartID:                 cart.ID,
   377  		MarketplaceCode:        item.MarketplaceCode,
   378  		VariantMarketplaceCode: item.VariantMarketPlaceCode,
   379  		ProductName:            product.TeaserData().ShortTitle,
   380  		QtyBefore:              qtyBefore,
   381  		QtyAfter:               qty,
   382  	}
   383  	defers = append(defers, updateEvent)
   384  
   385  	return nil
   386  }
   387  
   388  // UpdateItemSourceID updates an item source id
   389  func (cs *CartService) UpdateItemSourceID(ctx context.Context, session *web.Session, itemID string, sourceID string) error {
   390  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateItemSourceID")
   391  	defer span.End()
   392  
   393  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   394  	if err != nil {
   395  		return err
   396  	}
   397  	// cart cache must be updated - with the current value of cart
   398  	var defers cartDomain.DeferEvents
   399  	defer func() {
   400  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   401  		cs.dispatchAllEvents(ctx, defers)
   402  	}()
   403  
   404  	_, err = cart.GetByItemID(itemID)
   405  	if err != nil {
   406  		if !errors.Is(err, context.Canceled) {
   407  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemSourceId").Error(err)
   408  		}
   409  		return err
   410  	}
   411  
   412  	itemUpdate := cartDomain.ItemUpdateCommand{
   413  		SourceID: &sourceID,
   414  		ItemID:   itemID,
   415  	}
   416  
   417  	cart, defers, err = behaviour.UpdateItem(ctx, cart, itemUpdate)
   418  	if err != nil {
   419  		cs.handleCartNotFound(session, err)
   420  
   421  		if !errors.Is(err, context.Canceled) {
   422  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemSourceId").Error(err)
   423  		}
   424  
   425  		return err
   426  	}
   427  
   428  	return nil
   429  }
   430  
   431  // UpdateItems updates multiple items
   432  func (cs *CartService) UpdateItems(ctx context.Context, session *web.Session, updateCommands []cartDomain.ItemUpdateCommand) error {
   433  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateItems")
   434  	defer span.End()
   435  
   436  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   437  	if err != nil {
   438  		return err
   439  	}
   440  
   441  	for _, command := range updateCommands {
   442  		_, err := cart.GetByItemID(command.ItemID)
   443  		if err != nil {
   444  			return err
   445  		}
   446  	}
   447  
   448  	// cart cache must be updated - with the current value of cart
   449  	var defers cartDomain.DeferEvents
   450  	defer func() {
   451  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   452  		cs.dispatchAllEvents(ctx, defers)
   453  	}()
   454  
   455  	cart, defers, err = behaviour.UpdateItems(ctx, cart, updateCommands)
   456  	if err != nil {
   457  		cs.handleCartNotFound(session, err)
   458  		if !errors.Is(err, context.Canceled) {
   459  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemSourceId").Error(err)
   460  		}
   461  		return err
   462  	}
   463  
   464  	return nil
   465  }
   466  
   467  // UpdateItemBundleConfig updates multiple item
   468  func (cs *CartService) UpdateItemBundleConfig(ctx context.Context, session *web.Session, updateCommand cartDomain.ItemUpdateCommand) error {
   469  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateItemBundleConfig")
   470  	defer span.End()
   471  
   472  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   473  	if err != nil {
   474  		return err
   475  	}
   476  
   477  	if updateCommand.BundleConfiguration == nil {
   478  		return ErrBundleConfigNotProvided
   479  	}
   480  
   481  	item, err := cart.GetByItemID(updateCommand.ItemID)
   482  	if err != nil {
   483  		return err
   484  	}
   485  
   486  	product, err := cs.productService.Get(ctx, item.MarketplaceCode)
   487  	if err != nil {
   488  		if !errors.Is(err, context.Canceled) {
   489  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemBundleConfig").Error(err)
   490  		}
   491  		return err
   492  	}
   493  
   494  	product, err = cs.getBundleWithActiveChoices(ctx, product, updateCommand.BundleConfiguration)
   495  	if err != nil {
   496  		return fmt.Errorf("error converting product to bundle: %w", err)
   497  	}
   498  
   499  	if cs.itemValidator != nil {
   500  		decoratedCart, _ := cs.cartReceiverService.DecorateCart(ctx, cart)
   501  		delivery, err := cart.GetDeliveryByItemID(updateCommand.ItemID)
   502  		if err != nil {
   503  			return fmt.Errorf("delivery code not found by item, while updating bundle: %w", err)
   504  		}
   505  
   506  		if err := cs.itemValidator.Validate(ctx, session, decoratedCart, delivery.DeliveryInfo.Code, cartDomain.AddRequest{}, product); err != nil {
   507  			return fmt.Errorf("error validating bundle update: %w", err)
   508  		}
   509  	}
   510  
   511  	// cart cache must be updated - with the current value of cart
   512  	var defers cartDomain.DeferEvents
   513  	defer func() {
   514  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   515  		cs.dispatchAllEvents(ctx, defers)
   516  	}()
   517  
   518  	cart, defers, err = behaviour.UpdateItem(ctx, cart, updateCommand)
   519  	if err != nil {
   520  		cs.handleCartNotFound(session, err)
   521  		if !errors.Is(err, context.Canceled) {
   522  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "UpdateItemSourceId").Error(err)
   523  		}
   524  		return err
   525  	}
   526  
   527  	return nil
   528  }
   529  
   530  //nolint:wrapcheck // error wrapped in the code above, no need to wrap twice
   531  func (cs *CartService) getBundleWithActiveChoices(ctx context.Context, product productDomain.BasicProduct, bundleConfig productDomain.BundleConfiguration) (productDomain.BasicProduct, error) {
   532  	_, span := trace.StartSpan(ctx, "cart/CartService/getBundleWithActiveChoices")
   533  	defer span.End()
   534  
   535  	var err error
   536  
   537  	bundleProduct, ok := product.(productDomain.BundleProduct)
   538  	if !ok {
   539  		return nil, ErrProductNotTypeBundle
   540  	}
   541  
   542  	product, err = bundleProduct.GetBundleProductWithActiveChoices(bundleConfig)
   543  	if err != nil {
   544  		return nil, err
   545  	}
   546  
   547  	return product, nil
   548  }
   549  
   550  // DeleteItem in current cart
   551  func (cs *CartService) DeleteItem(ctx context.Context, session *web.Session, itemID string, deliveryCode string) error {
   552  	ctx, span := trace.StartSpan(ctx, "cart/CartService/DeleteItem")
   553  	defer span.End()
   554  
   555  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   556  	if err != nil {
   557  		return err
   558  	}
   559  	// cart cache must be updated - with the current value of cart
   560  	var defers cartDomain.DeferEvents
   561  	defer func() {
   562  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   563  
   564  		cs.handleEmptyDelivery(ctx, session, cart, deliveryCode)
   565  		cs.dispatchAllEvents(ctx, defers)
   566  	}()
   567  
   568  	if deliveryCode == "" {
   569  		deliveryCode = cs.defaultDeliveryCode
   570  	}
   571  
   572  	item, err := cart.GetByItemID(itemID)
   573  	if err != nil {
   574  		if !errors.Is(err, context.Canceled) {
   575  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "DeleteItem").Error(err)
   576  		}
   577  		return err
   578  	}
   579  
   580  	qtyBefore := item.Qty
   581  	cart, defers, err = behaviour.DeleteItem(ctx, cart, itemID, deliveryCode)
   582  	if err != nil {
   583  		cs.handleCartNotFound(session, err)
   584  		if !errors.Is(err, context.Canceled) {
   585  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "DeleteItem").Error(errors.Wrap(err, "Trying to delete SKU :"+item.MarketplaceCode))
   586  		}
   587  		return err
   588  	}
   589  
   590  	// append deferred events of behaviour with changed qty event
   591  	updateEvent := &events.ChangedQtyInCartEvent{
   592  		Cart:                   cart,
   593  		CartID:                 cart.ID,
   594  		MarketplaceCode:        item.MarketplaceCode,
   595  		VariantMarketplaceCode: item.VariantMarketPlaceCode,
   596  		ProductName:            item.ProductName,
   597  		QtyBefore:              qtyBefore,
   598  		QtyAfter:               0,
   599  	}
   600  	defers = append(defers, updateEvent)
   601  
   602  	return nil
   603  }
   604  
   605  // DeleteAllItems in current cart
   606  func (cs *CartService) DeleteAllItems(ctx context.Context, session *web.Session) error {
   607  	ctx, span := trace.StartSpan(ctx, "cart/CartService/DeleteAllItems")
   608  	defer span.End()
   609  
   610  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   611  	if err != nil {
   612  		return err
   613  	}
   614  	// cart cache must be updated - with the current value of cart
   615  	var defers cartDomain.DeferEvents
   616  	var deleteItemEvents cartDomain.DeferEvents
   617  	defer func() {
   618  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   619  		cs.dispatchAllEvents(ctx, defers)
   620  	}()
   621  
   622  	// we throw a qty changed event for every item in delivery
   623  	for _, delivery := range cart.Deliveries {
   624  		for _, item := range delivery.Cartitems {
   625  			updateEvent := &events.ChangedQtyInCartEvent{
   626  				Cart:                   cart,
   627  				CartID:                 cart.ID,
   628  				MarketplaceCode:        item.MarketplaceCode,
   629  				VariantMarketplaceCode: item.VariantMarketPlaceCode,
   630  				ProductName:            item.ProductName,
   631  				QtyBefore:              item.Qty,
   632  				QtyAfter:               0,
   633  			}
   634  			deleteItemEvents = append(deleteItemEvents, updateEvent)
   635  
   636  			cart, defers, err = behaviour.DeleteItem(ctx, cart, item.ID, delivery.DeliveryInfo.Code)
   637  			if err != nil {
   638  				cs.handleCartNotFound(session, err)
   639  				if !errors.Is(err, context.Canceled) {
   640  					cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "DeleteAllItems").Error(err)
   641  				}
   642  				return err
   643  			}
   644  		}
   645  	}
   646  	// append deferred events of behaviour with changed qty events
   647  	defers = append(defers, deleteItemEvents)
   648  
   649  	return nil
   650  }
   651  
   652  // CompleteCurrentCart and remove from cache
   653  func (cs *CartService) CompleteCurrentCart(ctx context.Context) (*cartDomain.Cart, error) {
   654  	ctx, span := trace.StartSpan(ctx, "cart/CartService/CompleteCurrentCart")
   655  	defer span.End()
   656  
   657  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, web.SessionFromContext(ctx))
   658  	if err != nil {
   659  		return nil, err
   660  	}
   661  
   662  	// dispatch potential events
   663  	var defers cartDomain.DeferEvents
   664  	defer func() {
   665  		cs.dispatchAllEvents(ctx, defers)
   666  	}()
   667  
   668  	completeBehaviour, ok := behaviour.(cartDomain.CompleteBehaviour)
   669  	if !ok {
   670  		return nil, fmt.Errorf("not supported by used cart behaviour: %T", behaviour)
   671  	}
   672  
   673  	var completedCart *cartDomain.Cart
   674  	completedCart, defers, err = completeBehaviour.Complete(ctx, cart)
   675  	if err != nil {
   676  		cs.handleCartNotFound(web.SessionFromContext(ctx), err)
   677  		if !errors.Is(err, context.Canceled) {
   678  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "CloseCurrentCart").Error(err)
   679  		}
   680  		return nil, err
   681  	}
   682  	cs.DeleteCartInCache(ctx, web.SessionFromContext(ctx), nil)
   683  
   684  	session := web.SessionFromContext(ctx)
   685  	if !cart.BelongsToAuthenticatedUser {
   686  		session.Delete(GuestCartSessionKey)
   687  	}
   688  
   689  	return completedCart, nil
   690  }
   691  
   692  // RestoreCart and cache
   693  func (cs *CartService) RestoreCart(ctx context.Context, cart *cartDomain.Cart) (*cartDomain.Cart, error) {
   694  	ctx, span := trace.StartSpan(ctx, "cart/CartService/RestoreCart")
   695  	defer span.End()
   696  
   697  	session := web.SessionFromContext(ctx)
   698  	behaviour, err := cs.cartReceiverService.ModifyBehaviour(ctx)
   699  	if err != nil {
   700  		return nil, err
   701  	}
   702  
   703  	completeBehaviour, ok := behaviour.(cartDomain.CompleteBehaviour)
   704  	if !ok {
   705  		return nil, fmt.Errorf("not supported by used cart behaviour: %T", behaviour)
   706  	}
   707  
   708  	// cart cache must be updated - with the current value of cart
   709  	var defers cartDomain.DeferEvents
   710  	var restoredCart *cartDomain.Cart
   711  	defer func() {
   712  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, restoredCart)
   713  		cs.dispatchAllEvents(ctx, defers)
   714  	}()
   715  
   716  	restoredCart, defers, err = completeBehaviour.Restore(ctx, cart)
   717  
   718  	if err != nil {
   719  		cs.handleCartNotFound(web.SessionFromContext(ctx), err)
   720  		if !errors.Is(err, context.Canceled) {
   721  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "RestoreCart").Error(err)
   722  		}
   723  		return nil, err
   724  	}
   725  
   726  	if !restoredCart.BelongsToAuthenticatedUser {
   727  		session.Store(GuestCartSessionKey, restoredCart.ID)
   728  	}
   729  
   730  	return restoredCart, nil
   731  }
   732  
   733  // Clean current cart
   734  func (cs *CartService) Clean(ctx context.Context, session *web.Session) error {
   735  	ctx, span := trace.StartSpan(ctx, "cart/CartService/Clean")
   736  	defer span.End()
   737  
   738  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   739  	if err != nil {
   740  		return err
   741  	}
   742  	// cart cache must be updated - with the current value of cart
   743  	var defers cartDomain.DeferEvents
   744  	var deleteItemEvents cartDomain.DeferEvents
   745  	defer func() {
   746  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   747  		cs.dispatchAllEvents(ctx, defers)
   748  	}()
   749  
   750  	// we throw a qty changed event for every item of each delivery
   751  	for _, delivery := range cart.Deliveries {
   752  		for _, item := range delivery.Cartitems {
   753  			updateEvent := &events.ChangedQtyInCartEvent{
   754  				Cart:                   cart,
   755  				CartID:                 cart.ID,
   756  				MarketplaceCode:        item.MarketplaceCode,
   757  				VariantMarketplaceCode: item.VariantMarketPlaceCode,
   758  				ProductName:            item.ProductName,
   759  				QtyBefore:              item.Qty,
   760  				QtyAfter:               0,
   761  			}
   762  			deleteItemEvents = append(deleteItemEvents, updateEvent)
   763  		}
   764  	}
   765  
   766  	cart, defers, err = behaviour.CleanCart(ctx, cart)
   767  	if err != nil {
   768  		if !errors.Is(err, context.Canceled) {
   769  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "DeleteAllItems").Error(err)
   770  		}
   771  		return err
   772  	}
   773  	// append deferred events of behaviour with changed qty events for every deleted item
   774  	defers = append(defers, deleteItemEvents)
   775  
   776  	return nil
   777  }
   778  
   779  // DeleteDelivery in current cart
   780  func (cs *CartService) DeleteDelivery(ctx context.Context, session *web.Session, deliveryCode string) (*cartDomain.Cart, error) {
   781  	ctx, span := trace.StartSpan(ctx, "cart/CartService/DeleteDelivery")
   782  	defer span.End()
   783  
   784  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   785  	if err != nil {
   786  		return nil, err
   787  	}
   788  	// cart cache must be updated - with the current value of cart
   789  	var defers cartDomain.DeferEvents
   790  	var deleteItemEvents cartDomain.DeferEvents
   791  	defer func() {
   792  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   793  		cs.dispatchAllEvents(ctx, defers)
   794  	}()
   795  
   796  	delivery, found := cart.GetDeliveryByCode(deliveryCode)
   797  	if !found {
   798  		return nil, errors.New("delivery not found: " + deliveryCode)
   799  	}
   800  	// we throw a qty changed event for every item in delivery// we throw a qty changed event for every item in delivery
   801  	for _, item := range delivery.Cartitems {
   802  		updateEvent := &events.ChangedQtyInCartEvent{
   803  			Cart:                   cart,
   804  			CartID:                 cart.ID,
   805  			MarketplaceCode:        item.MarketplaceCode,
   806  			VariantMarketplaceCode: item.VariantMarketPlaceCode,
   807  			ProductName:            item.ProductName,
   808  			QtyBefore:              item.Qty,
   809  			QtyAfter:               0,
   810  		}
   811  		deleteItemEvents = append(deleteItemEvents, updateEvent)
   812  	}
   813  
   814  	cart, defers, err = behaviour.CleanDelivery(ctx, cart, deliveryCode)
   815  	if err != nil {
   816  		if !errors.Is(err, context.Canceled) {
   817  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "DeleteAllItems").Error(err)
   818  		}
   819  		return nil, err
   820  	}
   821  
   822  	// append deferred events of behaviour with changed qty events for every deleted item
   823  	defers = append(defers, deleteItemEvents)
   824  
   825  	return cart, nil
   826  }
   827  
   828  // BuildAddRequest Helper to build
   829  // Deprecated: build your own add request
   830  func (cs *CartService) BuildAddRequest(ctx context.Context, marketplaceCode string, variantMarketplaceCode string, qty int, additionalData map[string]string) cartDomain.AddRequest {
   831  	_, span := trace.StartSpan(ctx, "cart/CartService/BuildAddRequest")
   832  	defer span.End()
   833  
   834  	if qty < 0 {
   835  		qty = 0
   836  	}
   837  
   838  	return cartDomain.AddRequest{
   839  		MarketplaceCode:        marketplaceCode,
   840  		Qty:                    qty,
   841  		VariantMarketplaceCode: variantMarketplaceCode,
   842  		AdditionalData:         additionalData,
   843  	}
   844  }
   845  
   846  // AddProduct adds a product to the cart
   847  func (cs *CartService) AddProduct(ctx context.Context, session *web.Session, deliveryCode string, addRequest cartDomain.AddRequest) (productDomain.BasicProduct, error) {
   848  	ctx, span := trace.StartSpan(ctx, "cart/CartService/AddProduct")
   849  	defer span.End()
   850  
   851  	if deliveryCode == "" {
   852  		deliveryCode = cs.defaultDeliveryCode
   853  	}
   854  
   855  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   856  	if err != nil {
   857  		if !errors.Is(err, context.Canceled) {
   858  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Error(err)
   859  		}
   860  		return nil, err
   861  	}
   862  	// cart cache must be updated - with the current value of cart
   863  	var defers cartDomain.DeferEvents
   864  	defer func() {
   865  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
   866  
   867  		cs.handleEmptyDelivery(ctx, session, cart, deliveryCode)
   868  		cs.dispatchAllEvents(ctx, defers)
   869  	}()
   870  
   871  	product, err := cs.checkProductForAddRequest(ctx, session, cart, deliveryCode, addRequest)
   872  
   873  	switch err.(type) {
   874  	case nil:
   875  	case *validation.AddToCartNotAllowed:
   876  		cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Info(err)
   877  		return nil, err
   878  	default:
   879  		cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Warn(err)
   880  		return nil, err
   881  	}
   882  
   883  	cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Debug(fmt.Sprintf("AddRequest received %#v  / %v", addRequest, deliveryCode))
   884  
   885  	cart, err = cs.CreateInitialDeliveryIfNotPresent(ctx, session, deliveryCode)
   886  	if err != nil {
   887  		if !errors.Is(err, context.Canceled) {
   888  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Error(err)
   889  		}
   890  		return nil, err
   891  	}
   892  
   893  	err = cs.checkProductQtyRestrictions(ctx, session, product, cart, addRequest.Qty, deliveryCode, "")
   894  	if err != nil {
   895  		cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Info(err)
   896  
   897  		return nil, err
   898  	}
   899  
   900  	cart, defers, err = behaviour.AddToCart(ctx, cart, deliveryCode, addRequest)
   901  	if err != nil {
   902  		cs.handleCartNotFound(session, err)
   903  		if !errors.Is(err, context.Canceled) {
   904  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "AddProduct").Error(err)
   905  		}
   906  
   907  		return nil, err
   908  	}
   909  
   910  	// append deferred events of behaviour with add to cart event
   911  	addToCart := &events.AddToCartEvent{
   912  		Cart:                   cart,
   913  		MarketplaceCode:        addRequest.MarketplaceCode,
   914  		VariantMarketplaceCode: addRequest.VariantMarketplaceCode,
   915  		ProductName:            product.TeaserData().ShortTitle,
   916  		Qty:                    addRequest.Qty,
   917  	}
   918  	defers = append(defers, addToCart)
   919  
   920  	return product, nil
   921  }
   922  
   923  // CreateInitialDeliveryIfNotPresent creates the initial delivery
   924  func (cs *CartService) CreateInitialDeliveryIfNotPresent(ctx context.Context, session *web.Session, deliveryCode string) (*cartDomain.Cart, error) {
   925  	ctx, span := trace.StartSpan(ctx, "cart/CartService/CreateInitialDeliveryIfNotPresent")
   926  	defer span.End()
   927  
   928  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
   929  	if err != nil {
   930  		return nil, err
   931  	}
   932  
   933  	if cart.HasDeliveryForCode(deliveryCode) {
   934  		return cart, nil
   935  	}
   936  
   937  	delInfo, err := cs.deliveryInfoBuilder.BuildByDeliveryCode(deliveryCode)
   938  	if err != nil {
   939  		return nil, err
   940  	}
   941  
   942  	updateCommand := cartDomain.DeliveryInfoUpdateCommand{
   943  		DeliveryInfo: *delInfo,
   944  	}
   945  
   946  	updatedCart, defers, err := behaviour.UpdateDeliveryInfo(ctx, cart, deliveryCode, updateCommand)
   947  	defer func() {
   948  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, updatedCart)
   949  		cs.dispatchAllEvents(ctx, defers)
   950  	}()
   951  
   952  	return updatedCart, err
   953  }
   954  
   955  // GetInitialDelivery - calls the registered deliveryInfoBuilder to get the initial values for a Delivery based on the given code
   956  func (cs *CartService) GetInitialDelivery(deliveryCode string) (*cartDomain.DeliveryInfo, error) {
   957  	return cs.deliveryInfoBuilder.BuildByDeliveryCode(deliveryCode)
   958  }
   959  
   960  // ApplyVoucher applies a voucher to the cart
   961  func (cs *CartService) ApplyVoucher(ctx context.Context, session *web.Session, couponCode string) (*cartDomain.Cart, error) {
   962  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ApplyVoucher")
   963  	defer span.End()
   964  
   965  	cart, behaviour, err := cs.getCartAndBehaviour(ctx, session, "ApplyVoucher")
   966  	if err != nil {
   967  		return nil, err
   968  	}
   969  	return cs.executeVoucherBehaviour(ctx, session, cart, couponCode, behaviour.ApplyVoucher)
   970  }
   971  
   972  // ApplyAny applies a voucher or giftcard to the cart
   973  func (cs *CartService) ApplyAny(ctx context.Context, session *web.Session, anyCode string) (*cartDomain.Cart, error) {
   974  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ApplyAny")
   975  	defer span.End()
   976  
   977  	cart, behaviour, err := cs.getCartAndBehaviour(ctx, session, "ApplyAny")
   978  	if err != nil {
   979  		return nil, err
   980  	}
   981  	if giftCardAndVoucherBehaviour, ok := behaviour.(cartDomain.GiftCardAndVoucherBehaviour); ok {
   982  		return cs.executeVoucherBehaviour(ctx, session, cart, anyCode, giftCardAndVoucherBehaviour.ApplyAny)
   983  	}
   984  	return nil, errors.New("ApplyAny not supported")
   985  }
   986  
   987  // RemoveVoucher removes a voucher from the cart
   988  func (cs *CartService) RemoveVoucher(ctx context.Context, session *web.Session, couponCode string) (*cartDomain.Cart, error) {
   989  	ctx, span := trace.StartSpan(ctx, "cart/CartService/RemoveVoucher")
   990  	defer span.End()
   991  
   992  	cart, behaviour, err := cs.getCartAndBehaviour(ctx, session, "RemoveVoucher")
   993  	if err != nil {
   994  		return nil, err
   995  	}
   996  	return cs.executeVoucherBehaviour(ctx, session, cart, couponCode, behaviour.RemoveVoucher)
   997  }
   998  
   999  // ApplyGiftCard adds a giftcard to the cart
  1000  func (cs *CartService) ApplyGiftCard(ctx context.Context, session *web.Session, couponCode string) (*cartDomain.Cart, error) {
  1001  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ApplyGiftCard")
  1002  	defer span.End()
  1003  
  1004  	cart, behaviour, err := cs.getCartAndBehaviour(ctx, session, "ApplyGiftCard")
  1005  	if err != nil {
  1006  		return nil, err
  1007  	}
  1008  	if giftCartBehaviour, ok := behaviour.(cartDomain.GiftCardBehaviour); ok {
  1009  		return cs.executeVoucherBehaviour(ctx, session, cart, couponCode, giftCartBehaviour.ApplyGiftCard)
  1010  	}
  1011  	return nil, errors.New("ApplyGiftCard not supported")
  1012  }
  1013  
  1014  // RemoveGiftCard removes a giftcard from the cart
  1015  func (cs *CartService) RemoveGiftCard(ctx context.Context, session *web.Session, couponCode string) (*cartDomain.Cart, error) {
  1016  	ctx, span := trace.StartSpan(ctx, "cart/CartService/RemoveGiftCard")
  1017  	defer span.End()
  1018  
  1019  	cart, behaviour, err := cs.getCartAndBehaviour(ctx, session, "RemoveGiftCard")
  1020  	if err != nil {
  1021  		return nil, err
  1022  	}
  1023  	if giftCartBehaviour, ok := behaviour.(cartDomain.GiftCardBehaviour); ok {
  1024  		return cs.executeVoucherBehaviour(ctx, session, cart, couponCode, giftCartBehaviour.RemoveGiftCard)
  1025  	}
  1026  	return nil, errors.New("RemoveGiftCard not supported")
  1027  }
  1028  
  1029  // Get current cart from session and corresponding behaviour
  1030  func (cs *CartService) getCartAndBehaviour(ctx context.Context, session *web.Session, logKey string) (*cartDomain.Cart, cartDomain.ModifyBehaviour, error) {
  1031  	ctx, span := trace.StartSpan(ctx, "cart/CartService/getCartAndBehaviour")
  1032  	defer span.End()
  1033  
  1034  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
  1035  	if err != nil {
  1036  		if !errors.Is(err, context.Canceled) {
  1037  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, logKey).Error(err)
  1038  		}
  1039  
  1040  		return nil, nil, err
  1041  	}
  1042  	return cart, behaviour, nil
  1043  }
  1044  
  1045  // Executes provided behaviour regarding vouchers, this function serves to reduce duplicated code
  1046  // for voucher / giftcard behaviour as their internal logic is basically the same
  1047  func (cs *CartService) executeVoucherBehaviour(ctx context.Context, session *web.Session, cart *cartDomain.Cart, couponCode string, fn promotionFunc) (*cartDomain.Cart, error) {
  1048  	ctx, span := trace.StartSpan(ctx, "cart/CartService/executeVoucherBehaviour")
  1049  	defer span.End()
  1050  
  1051  	// cart cache must be updated - with the current value of cart
  1052  	var defers cartDomain.DeferEvents
  1053  	defer func() {
  1054  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
  1055  		cs.dispatchAllEvents(ctx, defers)
  1056  	}()
  1057  	cart, defers, err := fn(ctx, cart, couponCode)
  1058  	return cart, err
  1059  }
  1060  
  1061  func (cs *CartService) handleCartNotFound(session *web.Session, err error) {
  1062  	if errors.Is(err, cartDomain.ErrCartNotFound) {
  1063  		_ = cs.DeleteSavedSessionGuestCartID(session)
  1064  	}
  1065  }
  1066  
  1067  // checkProductForAddRequest existence and validate with productService
  1068  func (cs *CartService) checkProductForAddRequest(ctx context.Context, session *web.Session, cart *cartDomain.Cart, deliveryCode string, addRequest cartDomain.AddRequest) (productDomain.BasicProduct, error) {
  1069  	ctx, span := trace.StartSpan(ctx, "cart/CartService/checkProductForAddRequest")
  1070  	defer span.End()
  1071  
  1072  	product, err := cs.productService.Get(ctx, addRequest.MarketplaceCode)
  1073  	if err != nil {
  1074  		return nil, err
  1075  	}
  1076  
  1077  	if product.Type() == productDomain.TypeConfigurable {
  1078  		if addRequest.VariantMarketplaceCode == "" {
  1079  			return nil, ErrNoVariantForConfigurable
  1080  		}
  1081  
  1082  		configurableProduct := product.(productDomain.ConfigurableProduct)
  1083  		if !configurableProduct.HasVariant(addRequest.VariantMarketplaceCode) {
  1084  			return nil, ErrVariantDoNotExist
  1085  		}
  1086  
  1087  		product, err = configurableProduct.GetConfigurableWithActiveVariant(addRequest.VariantMarketplaceCode)
  1088  		if err != nil {
  1089  			return nil, err
  1090  		}
  1091  	}
  1092  
  1093  	if product.Type() == productDomain.TypeBundle {
  1094  		if len(addRequest.BundleConfiguration) == 0 {
  1095  			return nil, ErrNoBundleConfigurationGiven
  1096  		}
  1097  
  1098  		bundleProduct := product.(productDomain.BundleProduct)
  1099  		domainBundleConfig := addRequest.BundleConfiguration
  1100  
  1101  		product, err = bundleProduct.GetBundleProductWithActiveChoices(domainBundleConfig)
  1102  		if err != nil {
  1103  			return nil, fmt.Errorf("error getting bundle with active choices: %w", err)
  1104  		}
  1105  	}
  1106  
  1107  	// Now Validate the Item with the optional registered ItemValidator
  1108  	if cs.itemValidator != nil {
  1109  		decoratedCart, _ := cs.cartReceiverService.DecorateCart(ctx, cart)
  1110  		return product, cs.itemValidator.Validate(ctx, session, decoratedCart, deliveryCode, addRequest, product)
  1111  	}
  1112  
  1113  	return product, nil
  1114  }
  1115  
  1116  func (cs *CartService) checkProductQtyRestrictions(ctx context.Context, sess *web.Session, product productDomain.BasicProduct, cart *cartDomain.Cart, qtyToCheck int, deliveryCode string, itemID string) error {
  1117  	ctx, span := trace.StartSpan(ctx, "cart/CartService/checkProductQtyRestrictions")
  1118  	defer span.End()
  1119  
  1120  	restrictionResult := cs.restrictionService.RestrictQty(ctx, sess, product, cart, deliveryCode)
  1121  
  1122  	if restrictionResult.IsRestricted {
  1123  		if qtyToCheck > restrictionResult.RemainingDifference {
  1124  			return &RestrictionError{
  1125  				message:           fmt.Sprintf("Can't update item quantity, product max quantity of %d would be exceeded. Restrictor: %v", restrictionResult.MaxAllowed, restrictionResult.RestrictorName),
  1126  				CartItemID:        itemID,
  1127  				RestrictionResult: *restrictionResult,
  1128  			}
  1129  		}
  1130  	}
  1131  
  1132  	return nil
  1133  }
  1134  
  1135  func (cs *CartService) updateCartInCacheIfCacheIsEnabled(ctx context.Context, session *web.Session, cart *cartDomain.Cart) {
  1136  	ctx, span := trace.StartSpan(ctx, "cart/CartService/updateCartInCacheIfCacheIsEnabled")
  1137  	defer span.End()
  1138  
  1139  	if cs.cartCache != nil && cart != nil {
  1140  		id, err := cs.cartCache.BuildIdentifier(ctx, session)
  1141  		if err != nil {
  1142  			return
  1143  		}
  1144  		if cart.BelongsToAuthenticatedUser != id.IsCustomerCart {
  1145  			cs.logger.WithContext(ctx).Error(fmt.Sprintf("Request to cache a cart with wrong idendifier. Cart BelongsToAuthenticatedUser: %v / CacheIdendifier: %v", cart.BelongsToAuthenticatedUser, id.IsCustomerCart))
  1146  			return
  1147  		}
  1148  
  1149  		err = cs.cartCache.CacheCart(ctx, session, id, cart)
  1150  		if err != nil {
  1151  			cs.logger.WithContext(ctx).Error("Error while caching cart: %v", err)
  1152  		}
  1153  	}
  1154  }
  1155  
  1156  // DeleteCartInCache removes the cart from cache
  1157  func (cs *CartService) DeleteCartInCache(ctx context.Context, session *web.Session, _ *cartDomain.Cart) {
  1158  	ctx, span := trace.StartSpan(ctx, "cart/CartService/DeleteCartInCache")
  1159  	defer span.End()
  1160  
  1161  	if cs.cartCache != nil {
  1162  		id, err := cs.cartCache.BuildIdentifier(ctx, session)
  1163  		if err != nil {
  1164  			return
  1165  		}
  1166  
  1167  		err = cs.cartCache.Delete(ctx, session, id)
  1168  		if errors.Is(err, ErrNoCacheEntry) {
  1169  			cs.logger.WithContext(ctx).Info("Cart in cache not found: %v", err)
  1170  			return
  1171  		}
  1172  		if err != nil {
  1173  			cs.logger.WithContext(ctx).Error("Error while deleting cart in cache: %v", err)
  1174  		}
  1175  	}
  1176  }
  1177  
  1178  // ReserveOrderIDAndSave reserves order id by using the PlaceOrder behaviour, sets and saves it on the cart.
  1179  // If the cart already holds a reserved order id no set/save is performed and the existing cart is returned.
  1180  // You may want to use this before proceeding with payment to ensure having a useful reference in the payment processing
  1181  func (cs *CartService) ReserveOrderIDAndSave(ctx context.Context, session *web.Session) (*cartDomain.Cart, error) {
  1182  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ReserveOrderIDAndSave")
  1183  	defer span.End()
  1184  
  1185  	return cs.reserveOrderIDAndSave(ctx, session, false)
  1186  }
  1187  
  1188  // ForceReserveOrderIDAndSave reserves order id by using the PlaceOrder behaviour, sets and saves it on the cart.
  1189  // Each call of this method reserves a new order ID, even if it is already set on the cart.
  1190  // You may want to use this before proceeding with payment to ensure having a useful reference in the payment processing
  1191  func (cs *CartService) ForceReserveOrderIDAndSave(ctx context.Context, session *web.Session) (*cartDomain.Cart, error) {
  1192  	ctx, span := trace.StartSpan(ctx, "cart/CartService/ForceReserveOrderIDAndSave")
  1193  	defer span.End()
  1194  
  1195  	return cs.reserveOrderIDAndSave(ctx, session, true)
  1196  }
  1197  
  1198  func (cs *CartService) reserveOrderIDAndSave(ctx context.Context, session *web.Session, force bool) (*cartDomain.Cart, error) {
  1199  	ctx, span := trace.StartSpan(ctx, "cart/CartService/reserveOrderIDAndSave")
  1200  	defer span.End()
  1201  
  1202  	if cs.placeOrderService == nil {
  1203  		return nil, errors.New("No placeOrderService registered")
  1204  	}
  1205  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
  1206  	if err != nil {
  1207  		return nil, err
  1208  	}
  1209  
  1210  	// the cart already has a reserved order id - no need to generate and update data, if force parameter is true
  1211  	// a new reserved order id will always be generated
  1212  	if !force && cart.AdditionalData.ReservedOrderID != "" {
  1213  		return cart, nil
  1214  	}
  1215  
  1216  	reservedOrderID, err := cs.placeOrderService.ReserveOrderID(ctx, cart)
  1217  	if err != nil {
  1218  		cs.logger.WithContext(ctx).Debug("Reserve order id:", reservedOrderID)
  1219  		return nil, err
  1220  	}
  1221  	additionalData := cart.AdditionalData
  1222  	additionalData.ReservedOrderID = reservedOrderID
  1223  	cart, defers, err := behaviour.UpdateAdditionalData(ctx, cart, &additionalData)
  1224  	defer func() {
  1225  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
  1226  		cs.dispatchAllEvents(ctx, defers)
  1227  	}()
  1228  	return cart, err
  1229  }
  1230  
  1231  // PlaceOrderWithCart converts the given cart with payments into orders by calling the Service
  1232  func (cs *CartService) PlaceOrderWithCart(ctx context.Context, session *web.Session, cart *cartDomain.Cart, payment *placeorder.Payment) (placeorder.PlacedOrderInfos, error) {
  1233  	ctx, span := trace.StartSpan(ctx, "cart/CartService/PlaceOrderWithCart")
  1234  	defer span.End()
  1235  
  1236  	return cs.placeOrder(ctx, session, cart, payment)
  1237  }
  1238  
  1239  // PlaceOrder converts the cart (possibly cached) with payments into orders by calling the Service
  1240  func (cs *CartService) PlaceOrder(ctx context.Context, session *web.Session, payment *placeorder.Payment) (placeorder.PlacedOrderInfos, error) {
  1241  	ctx, span := trace.StartSpan(ctx, "cart/CartService/PlaceOrder")
  1242  	defer span.End()
  1243  
  1244  	cart, _, err := cs.cartReceiverService.GetCart(ctx, session)
  1245  	if err != nil {
  1246  		return nil, err
  1247  	}
  1248  	return cs.placeOrder(ctx, session, cart, payment)
  1249  }
  1250  
  1251  func (cs *CartService) placeOrder(ctx context.Context, session *web.Session, cart *cartDomain.Cart, payment *placeorder.Payment) (placeorder.PlacedOrderInfos, error) {
  1252  	ctx, span := trace.StartSpan(ctx, "cart/CartService/placeOrder")
  1253  	defer span.End()
  1254  
  1255  	if cs.placeOrderService == nil {
  1256  		return nil, errors.New("No placeOrderService registered")
  1257  	}
  1258  	var placeOrderInfos placeorder.PlacedOrderInfos
  1259  	var errPlaceOrder error
  1260  
  1261  	identity := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
  1262  
  1263  	if identity != nil {
  1264  		placeOrderInfos, errPlaceOrder = cs.placeOrderService.PlaceCustomerCart(ctx, identity, cart, payment)
  1265  	} else {
  1266  		placeOrderInfos, errPlaceOrder = cs.placeOrderService.PlaceGuestCart(ctx, cart, payment)
  1267  	}
  1268  
  1269  	if errPlaceOrder != nil {
  1270  		cs.handleCartNotFound(session, errPlaceOrder)
  1271  		return nil, errPlaceOrder
  1272  	}
  1273  
  1274  	cs.eventPublisher.PublishOrderPlacedEvent(ctx, cart, placeOrderInfos)
  1275  	_ = cs.DeleteSavedSessionGuestCartID(session)
  1276  	cs.DeleteCartInCache(ctx, session, cart)
  1277  
  1278  	return placeOrderInfos, nil
  1279  }
  1280  
  1281  // CancelOrder cancels a previously placed order and restores the cart content
  1282  func (cs *CartService) CancelOrder(ctx context.Context, session *web.Session, orderInfos placeorder.PlacedOrderInfos, cart cartDomain.Cart) (*cartDomain.Cart, error) {
  1283  	ctx, span := trace.StartSpan(ctx, "cart/CartService/CancelOrder")
  1284  	defer span.End()
  1285  
  1286  	err := cs.cancelOrder(ctx, session, orderInfos)
  1287  	if err != nil {
  1288  		return nil, err
  1289  	}
  1290  
  1291  	restoredCart, err := cs.RestoreCart(ctx, &cart)
  1292  	if err != nil {
  1293  		if !errors.Is(err, context.Canceled) {
  1294  			cs.logger.Error(fmt.Sprintf("couldn't restore cart err: %v", err))
  1295  		}
  1296  		return nil, err
  1297  	}
  1298  
  1299  	return restoredCart, nil
  1300  }
  1301  
  1302  func (cs *CartService) cancelOrder(ctx context.Context, _ *web.Session, orderInfos placeorder.PlacedOrderInfos) error {
  1303  	ctx, span := trace.StartSpan(ctx, "cart/CartService/cancelOrder")
  1304  	defer span.End()
  1305  
  1306  	if cs.placeOrderService == nil {
  1307  		return errors.New("No placeOrderService registered")
  1308  	}
  1309  
  1310  	var err error
  1311  
  1312  	identity := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
  1313  	if identity != nil {
  1314  		err = cs.placeOrderService.CancelCustomerOrder(ctx, orderInfos, identity)
  1315  	} else {
  1316  		err = cs.placeOrderService.CancelGuestOrder(ctx, orderInfos)
  1317  	}
  1318  
  1319  	if err != nil {
  1320  		err = fmt.Errorf("couldn't cancel order %q, err: %w", orderInfos, err)
  1321  
  1322  		if !errors.Is(err, context.Canceled) {
  1323  			cs.logger.Error(err)
  1324  		}
  1325  
  1326  		return err
  1327  	}
  1328  	return nil
  1329  }
  1330  
  1331  // CancelOrderWithoutRestore cancels a previously placed order
  1332  func (cs *CartService) CancelOrderWithoutRestore(ctx context.Context, session *web.Session, orderInfos placeorder.PlacedOrderInfos) error {
  1333  	ctx, span := trace.StartSpan(ctx, "cart/CartService/CancelOrderWithoutRestore")
  1334  	defer span.End()
  1335  
  1336  	return cs.cancelOrder(ctx, session, orderInfos)
  1337  }
  1338  
  1339  // GetDefaultDeliveryCode returns the configured default deliverycode
  1340  func (cs *CartService) GetDefaultDeliveryCode() string {
  1341  	return cs.defaultDeliveryCode
  1342  }
  1343  
  1344  // handleEmptyDelivery - delete an empty delivery when found and feature flag is set
  1345  func (cs *CartService) handleEmptyDelivery(ctx context.Context, session *web.Session, cart *cartDomain.Cart, deliveryCode string) {
  1346  	ctx, span := trace.StartSpan(ctx, "cart/CartService/handleEmptyDelivery")
  1347  	defer span.End()
  1348  
  1349  	if !cs.deleteEmptyDelivery {
  1350  		return
  1351  	}
  1352  
  1353  	if cart == nil {
  1354  		return
  1355  	}
  1356  
  1357  	delivery, found := cart.GetDeliveryByCode(deliveryCode)
  1358  	if !found {
  1359  		return
  1360  	}
  1361  
  1362  	if len(delivery.Cartitems) > 0 {
  1363  		return
  1364  	}
  1365  
  1366  	_, err := cs.DeleteDelivery(ctx, session, deliveryCode)
  1367  	if err != nil {
  1368  		if !errors.Is(err, context.Canceled) {
  1369  			cs.logger.WithContext(ctx).WithField(flamingo.LogKeySubCategory, "handleEmptyDelivery").Error(err)
  1370  		}
  1371  		return
  1372  	}
  1373  }
  1374  
  1375  func (cs *CartService) dispatchAllEvents(ctx context.Context, events []flamingo.Event) {
  1376  	ctx, span := trace.StartSpan(ctx, "cart/CartService/dispatchAllEvents")
  1377  	defer span.End()
  1378  
  1379  	for _, e := range events {
  1380  		cs.eventRouter.Dispatch(ctx, e)
  1381  	}
  1382  }
  1383  
  1384  // AdjustItemsToRestrictedQty checks the quantity restrictions for each item of the cart and returns what quantities have been adjusted
  1385  func (cs *CartService) AdjustItemsToRestrictedQty(ctx context.Context, session *web.Session) (QtyAdjustmentResults, error) {
  1386  	ctx, span := trace.StartSpan(ctx, "cart/CartService/AdjustItemsToRestrictedQty")
  1387  	defer span.End()
  1388  
  1389  	qtyAdjustmentResults, err := cs.generateRestrictedQtyAdjustments(ctx, session)
  1390  	if err != nil {
  1391  		return nil, err
  1392  	}
  1393  
  1394  	cart, _, err := cs.cartReceiverService.GetCart(ctx, session)
  1395  	if err != nil {
  1396  		return nil, err
  1397  	}
  1398  
  1399  	for index, qtyAdjustmentResult := range qtyAdjustmentResults {
  1400  		couponCodes := cart.AppliedCouponCodes
  1401  		if qtyAdjustmentResult.NewQty < 1 {
  1402  			err = cs.DeleteItem(ctx, session, qtyAdjustmentResult.OriginalItem.ID, qtyAdjustmentResult.DeliveryCode)
  1403  		} else {
  1404  			err = cs.UpdateItemQty(ctx, session, qtyAdjustmentResult.OriginalItem.ID, qtyAdjustmentResult.DeliveryCode, qtyAdjustmentResult.NewQty)
  1405  		}
  1406  		if err != nil {
  1407  			return nil, err
  1408  		}
  1409  		cart, _, err = cs.cartReceiverService.GetCart(ctx, session)
  1410  		if err != nil {
  1411  			return nil, err
  1412  		}
  1413  
  1414  		qtyAdjustmentResult.HasRemovedCouponCodes = !cartDomain.AppliedCouponCodes(couponCodes).ContainedIn(cart.AppliedCouponCodes)
  1415  		qtyAdjustmentResults[index] = qtyAdjustmentResult
  1416  	}
  1417  
  1418  	return qtyAdjustmentResults, nil
  1419  }
  1420  
  1421  // generateRestrictedQtyAdjustments checks the quantity restrictions for each item of the cart and returns which items should be adjusted and how
  1422  func (cs *CartService) generateRestrictedQtyAdjustments(ctx context.Context, session *web.Session) (QtyAdjustmentResults, error) {
  1423  	ctx, span := trace.StartSpan(ctx, "cart/CartService/generateRestrictedQtyAdjustments")
  1424  	defer span.End()
  1425  
  1426  	cart, _, err := cs.cartReceiverService.GetCart(ctx, session)
  1427  	if err != nil {
  1428  		return nil, err
  1429  	}
  1430  
  1431  	result := make([]QtyAdjustmentResult, 0)
  1432  	for _, delivery := range cart.Deliveries {
  1433  		for _, item := range delivery.Cartitems {
  1434  			product, err := cs.productService.Get(ctx, item.MarketplaceCode)
  1435  			if err != nil {
  1436  				return nil, err
  1437  			}
  1438  
  1439  			product, err = cs.getSpecificProductType(ctx, product, item.VariantMarketPlaceCode, item.BundleConfig)
  1440  			if err != nil {
  1441  				return nil, err
  1442  			}
  1443  
  1444  			itemContext := ContextWithItemID(ctx, item.ID)
  1445  			restrictionResult := cs.restrictionService.RestrictQty(itemContext, session, product, cart, delivery.DeliveryInfo.Code)
  1446  
  1447  			if restrictionResult.RemainingDifference >= 0 {
  1448  				continue
  1449  			}
  1450  
  1451  			newQty := item.Qty + restrictionResult.RemainingDifference
  1452  
  1453  			result = append(result, QtyAdjustmentResult{
  1454  				item,
  1455  				delivery.DeliveryInfo.Code,
  1456  				newQty < 1,
  1457  				restrictionResult,
  1458  				newQty,
  1459  				false,
  1460  			})
  1461  		}
  1462  	}
  1463  
  1464  	return result, nil
  1465  }
  1466  
  1467  func (cs *CartService) getSpecificProductType(ctx context.Context, product productDomain.BasicProduct, variantMarketplaceCode string, bundleConfig productDomain.BundleConfiguration) (productDomain.BasicProduct, error) {
  1468  	_, span := trace.StartSpan(ctx, "cart/CartService/getSpecificProductType")
  1469  	defer span.End()
  1470  
  1471  	var err error
  1472  
  1473  	if product.Type() != productDomain.TypeConfigurable && product.Type() != productDomain.TypeBundle {
  1474  		return product, nil
  1475  	}
  1476  
  1477  	if variantMarketplaceCode == "" {
  1478  		return product, nil
  1479  	}
  1480  
  1481  	if configurableProduct, ok := product.(productDomain.ConfigurableProduct); ok {
  1482  		product, err = configurableProduct.GetConfigurableWithActiveVariant(variantMarketplaceCode)
  1483  
  1484  		if err != nil {
  1485  			return nil, err
  1486  		}
  1487  	}
  1488  
  1489  	if bundleProduct, ok := product.(productDomain.BundleProduct); ok {
  1490  		product, err = bundleProduct.GetBundleProductWithActiveChoices(bundleConfig)
  1491  		if err != nil {
  1492  			return nil, err
  1493  		}
  1494  	}
  1495  
  1496  	return product, nil
  1497  }
  1498  
  1499  // HasRemovedCouponCodes returns if a QtyAdjustmentResults has any adjustment that removed a coupon code from the cart
  1500  func (qar QtyAdjustmentResults) HasRemovedCouponCodes() bool {
  1501  	for _, qtyAdjustmentResult := range qar {
  1502  		if qtyAdjustmentResult.HasRemovedCouponCodes {
  1503  			return true
  1504  		}
  1505  	}
  1506  
  1507  	return false
  1508  }
  1509  
  1510  // UpdateAdditionalData of cart
  1511  func (cs *CartService) UpdateAdditionalData(ctx context.Context, session *web.Session, additionalData map[string]string) (*cartDomain.Cart, error) {
  1512  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateAdditionalData")
  1513  	defer span.End()
  1514  
  1515  	cart, behaviour, err := cs.cartReceiverService.GetCart(ctx, session)
  1516  	if err != nil {
  1517  		return nil, err
  1518  	}
  1519  
  1520  	cartAdditionalData := cart.AdditionalData
  1521  	if cartAdditionalData.CustomAttributes == nil {
  1522  		cartAdditionalData.CustomAttributes = map[string]string{}
  1523  	}
  1524  
  1525  	for key, value := range additionalData {
  1526  		cartAdditionalData.CustomAttributes[key] = value
  1527  	}
  1528  
  1529  	cart, defers, err := behaviour.UpdateAdditionalData(ctx, cart, &cartAdditionalData)
  1530  	defer func() {
  1531  		cs.updateCartInCacheIfCacheIsEnabled(ctx, session, cart)
  1532  		cs.dispatchAllEvents(ctx, defers)
  1533  	}()
  1534  	return cart, err
  1535  }
  1536  
  1537  // UpdateDeliveryAdditionalData of cart
  1538  func (cs *CartService) UpdateDeliveryAdditionalData(ctx context.Context, session *web.Session, deliveryCode string, additionalData map[string]string) (*cartDomain.Cart, error) {
  1539  	ctx, span := trace.StartSpan(ctx, "cart/CartService/UpdateDeliveryAdditionalData")
  1540  	defer span.End()
  1541  
  1542  	cart, _, err := cs.cartReceiverService.GetCart(ctx, session)
  1543  	if err != nil {
  1544  		return nil, err
  1545  	}
  1546  
  1547  	delivery, found := cart.GetDeliveryByCode(deliveryCode)
  1548  	if !found {
  1549  		return cart, nil
  1550  	}
  1551  
  1552  	if delivery.DeliveryInfo.AdditionalData == nil {
  1553  		delivery.DeliveryInfo.AdditionalData = map[string]string{}
  1554  	}
  1555  
  1556  	for key, value := range additionalData {
  1557  		delivery.DeliveryInfo.AdditionalData[key] = value
  1558  	}
  1559  	newDeliveryInfoUpdateCommand := cartDomain.CreateDeliveryInfoUpdateCommand(delivery.DeliveryInfo)
  1560  
  1561  	err = cs.UpdateDeliveryInfo(ctx, session, deliveryCode, newDeliveryInfoUpdateCommand)
  1562  	if err != nil {
  1563  		return nil, err
  1564  	}
  1565  
  1566  	cart, _, err = cs.cartReceiverService.GetCart(ctx, session)
  1567  	return cart, err
  1568  }