flamingo.me/flamingo-commerce/v3@v3.11.0/cart/infrastructure/defaultCartBehaviour.go (about)

     1  package infrastructure
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  	"math/rand"
     8  	"strconv"
     9  
    10  	"go.opencensus.io/trace"
    11  
    12  	"flamingo.me/flamingo/v3/framework/flamingo"
    13  	"github.com/pkg/errors"
    14  
    15  	domaincart "flamingo.me/flamingo-commerce/v3/cart/domain/cart"
    16  	"flamingo.me/flamingo-commerce/v3/cart/domain/decorator"
    17  	"flamingo.me/flamingo-commerce/v3/cart/domain/events"
    18  	priceDomain "flamingo.me/flamingo-commerce/v3/price/domain"
    19  	"flamingo.me/flamingo-commerce/v3/product/domain"
    20  )
    21  
    22  //go:generate go run github.com/vektra/mockery/v2@v2.42.3 --name GiftCardHandler --case snake
    23  //go:generate go run github.com/vektra/mockery/v2@v2.42.3 --name VoucherHandler --case snake
    24  
    25  type (
    26  	// DefaultCartBehaviour defines the default cart order behaviour
    27  	DefaultCartBehaviour struct {
    28  		cartStorage     CartStorage
    29  		productService  domain.ProductService
    30  		logger          flamingo.Logger
    31  		giftCardHandler GiftCardHandler
    32  		voucherHandler  VoucherHandler
    33  		defaultTaxRate  float64
    34  		grossPricing    bool
    35  		defaultCurrency string
    36  	}
    37  
    38  	// CartStorage Interface - might be implemented by other persistence types later as well
    39  	CartStorage interface {
    40  		GetCart(ctx context.Context, id string) (*domaincart.Cart, error)
    41  		HasCart(ctx context.Context, id string) bool
    42  		StoreCart(ctx context.Context, cart *domaincart.Cart) error
    43  		RemoveCart(ctx context.Context, cart *domaincart.Cart) error
    44  	}
    45  
    46  	// GiftCardHandler enables the projects to have specific GiftCard handling
    47  	GiftCardHandler interface {
    48  		ApplyGiftCard(ctx context.Context, cart *domaincart.Cart, giftCardCode string) (*domaincart.Cart, error)
    49  		RemoveGiftCard(ctx context.Context, cart *domaincart.Cart, giftCardCode string) (*domaincart.Cart, error)
    50  	}
    51  
    52  	// VoucherHandler enables the projects to have specific Voucher handling
    53  	VoucherHandler interface {
    54  		ApplyVoucher(ctx context.Context, cart *domaincart.Cart, couponCode string) (*domaincart.Cart, error)
    55  		RemoveVoucher(ctx context.Context, cart *domaincart.Cart, couponCode string) (*domaincart.Cart, error)
    56  	}
    57  
    58  	// DefaultGiftCardHandler implements a basic gift card handler
    59  	DefaultGiftCardHandler struct{}
    60  
    61  	// DefaultVoucherHandler implements a basic voucher handler
    62  	DefaultVoucherHandler struct{}
    63  )
    64  
    65  var (
    66  	_ domaincart.ModifyBehaviour             = (*DefaultCartBehaviour)(nil)
    67  	_ domaincart.GiftCardAndVoucherBehaviour = (*DefaultCartBehaviour)(nil)
    68  	_ domaincart.CompleteBehaviour           = (*DefaultCartBehaviour)(nil)
    69  	_ GiftCardHandler                        = (*DefaultGiftCardHandler)(nil)
    70  	_ VoucherHandler                         = (*DefaultVoucherHandler)(nil)
    71  )
    72  
    73  const (
    74  	logCategory = "DefaultCartBehaviour"
    75  )
    76  
    77  // Inject dependencies
    78  func (cob *DefaultCartBehaviour) Inject(
    79  	cartStorage CartStorage,
    80  	productService domain.ProductService,
    81  	logger flamingo.Logger,
    82  	voucherHandler VoucherHandler,
    83  	giftCardHandler GiftCardHandler,
    84  	config *struct {
    85  		DefaultTaxRate  float64 `inject:"config:commerce.cart.defaultCartAdapter.defaultTaxRate,optional"`
    86  		ProductPricing  string  `inject:"config:commerce.cart.defaultCartAdapter.productPrices"`
    87  		DefaultCurrency string  `inject:"config:commerce.cart.defaultCartAdapter.defaultCurrency"`
    88  	},
    89  ) {
    90  	cob.cartStorage = cartStorage
    91  	cob.productService = productService
    92  	cob.logger = logger
    93  	cob.voucherHandler = voucherHandler
    94  	cob.giftCardHandler = giftCardHandler
    95  
    96  	if config != nil {
    97  		cob.defaultTaxRate = config.DefaultTaxRate
    98  		cob.defaultCurrency = config.DefaultCurrency
    99  
   100  		if config.ProductPricing == "gross" {
   101  			cob.grossPricing = true
   102  		}
   103  	}
   104  }
   105  
   106  // Complete a cart and remove from storage
   107  func (cob *DefaultCartBehaviour) Complete(ctx context.Context, cart *domaincart.Cart) (*domaincart.Cart, domaincart.DeferEvents, error) {
   108  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/Complete")
   109  	defer span.End()
   110  
   111  	err := cob.cartStorage.RemoveCart(ctx, cart)
   112  	if err != nil {
   113  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error removing cart: %w", err)
   114  	}
   115  
   116  	return cart, nil, nil
   117  }
   118  
   119  // Restore supplied cart (implements CompleteBehaviour)
   120  func (cob *DefaultCartBehaviour) Restore(ctx context.Context, cart *domaincart.Cart) (*domaincart.Cart, domaincart.DeferEvents, error) {
   121  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/Restore")
   122  	defer span.End()
   123  
   124  	newCart, err := cart.Clone()
   125  	if err != nil {
   126  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   127  	}
   128  
   129  	err = cob.collectTotals(cart)
   130  	if err != nil {
   131  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   132  	}
   133  
   134  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   135  	if err != nil {
   136  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   137  	}
   138  
   139  	return &newCart, nil, nil
   140  }
   141  
   142  // DeleteItem removes an item from the cart
   143  func (cob *DefaultCartBehaviour) DeleteItem(ctx context.Context, cart *domaincart.Cart, itemID string, deliveryCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   144  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/DeleteItem")
   145  	defer span.End()
   146  
   147  	if !cob.cartStorage.HasCart(ctx, cart.ID) {
   148  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: %w for cart id %q during delete", domaincart.ErrCartNotFound, cart.ID)
   149  	}
   150  
   151  	newCart, err := cart.Clone()
   152  	if err != nil {
   153  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   154  	}
   155  
   156  	if newDelivery, ok := newCart.GetDeliveryByCode(deliveryCode); ok {
   157  		cob.logger.WithContext(ctx).WithField(flamingo.LogKeyCategory, logCategory).Info("DefaultCartBehaviour Delete %v in %#v", itemID, newDelivery.Cartitems)
   158  
   159  		for index, item := range newDelivery.Cartitems {
   160  			if item.ID == itemID {
   161  				newDelivery.Cartitems = append(newDelivery.Cartitems[:index], newDelivery.Cartitems[index+1:]...)
   162  				break
   163  			}
   164  		}
   165  
   166  		// update the delivery with the new info
   167  		for index, delivery := range newCart.Deliveries {
   168  			if deliveryCode == delivery.DeliveryInfo.Code {
   169  				newCart.Deliveries[index] = *newDelivery
   170  			}
   171  		}
   172  	}
   173  
   174  	err = cob.collectTotals(&newCart)
   175  	if err != nil {
   176  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   177  	}
   178  
   179  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   180  	if err != nil {
   181  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   182  	}
   183  
   184  	return cob.resetPaymentSelectionIfInvalid(ctx, &newCart)
   185  }
   186  
   187  // UpdateItem updates a cart item
   188  func (cob *DefaultCartBehaviour) UpdateItem(ctx context.Context, cart *domaincart.Cart, itemUpdateCommand domaincart.ItemUpdateCommand) (*domaincart.Cart, domaincart.DeferEvents, error) {
   189  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdateItem")
   190  	defer span.End()
   191  
   192  	return cob.UpdateItems(ctx, cart, []domaincart.ItemUpdateCommand{itemUpdateCommand})
   193  }
   194  
   195  // UpdateItems updates multiple cart items
   196  func (cob *DefaultCartBehaviour) UpdateItems(ctx context.Context, cart *domaincart.Cart, itemUpdateCommands []domaincart.ItemUpdateCommand) (*domaincart.Cart, domaincart.DeferEvents, error) {
   197  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdateItems")
   198  	defer span.End()
   199  
   200  	if !cob.cartStorage.HasCart(ctx, cart.ID) {
   201  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: %w for cart id %q during update", domaincart.ErrCartNotFound, cart.ID)
   202  	}
   203  
   204  	newCart, err := cart.Clone()
   205  	if err != nil {
   206  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   207  	}
   208  
   209  	for _, itemUpdateCommand := range itemUpdateCommands {
   210  		err := cob.updateItem(ctx, &newCart, itemUpdateCommand)
   211  		if err != nil {
   212  			return nil, nil, err
   213  		}
   214  	}
   215  
   216  	err = cob.collectTotals(&newCart)
   217  	if err != nil {
   218  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   219  	}
   220  
   221  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   222  	if err != nil {
   223  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   224  	}
   225  
   226  	return cob.resetPaymentSelectionIfInvalid(ctx, &newCart)
   227  }
   228  
   229  func (cob *DefaultCartBehaviour) updateItem(ctx context.Context, cart *domaincart.Cart, itemUpdateCommand domaincart.ItemUpdateCommand) error {
   230  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/updateItem")
   231  	defer span.End()
   232  
   233  	itemDelivery, err := cart.GetDeliveryByItemID(itemUpdateCommand.ItemID)
   234  	if err != nil {
   235  		return fmt.Errorf("DefaultCartBehaviour: error finding delivery of item: %w", err)
   236  	}
   237  
   238  	cob.logger.WithContext(ctx).WithField(flamingo.LogKeyCategory, logCategory).Info("DefaultCartBehaviour Update %v in %#v", itemUpdateCommand.ItemID, itemDelivery.Cartitems)
   239  
   240  	for index, item := range itemDelivery.Cartitems {
   241  		if itemUpdateCommand.ItemID != item.ID {
   242  			continue
   243  		}
   244  
   245  		if itemUpdateCommand.SourceID != nil {
   246  			itemDelivery.Cartitems[index].SourceID = *itemUpdateCommand.SourceID
   247  		}
   248  
   249  		if itemUpdateCommand.AdditionalData != nil {
   250  			itemDelivery.Cartitems[index].AdditionalData = itemUpdateCommand.AdditionalData
   251  		}
   252  
   253  		if itemUpdateCommand.BundleConfiguration != nil {
   254  			itemDelivery.Cartitems[index].BundleConfig = itemUpdateCommand.BundleConfiguration
   255  		}
   256  
   257  		if itemUpdateCommand.Qty == nil {
   258  			break
   259  		}
   260  
   261  		// in case of qty 0 remove the item from the delivery
   262  		if *itemUpdateCommand.Qty == 0 {
   263  			itemDelivery.Cartitems = append(itemDelivery.Cartitems[:index], itemDelivery.Cartitems[index+1:]...)
   264  			break
   265  		}
   266  
   267  		itemDelivery.Cartitems[index].Qty = *itemUpdateCommand.Qty
   268  
   269  		gross := item.SinglePriceGross.Clone().Amount().Mul(item.SinglePriceGross.Amount(), big.NewFloat(float64(*itemUpdateCommand.Qty)))
   270  		itemDelivery.Cartitems[index].RowPriceGross = priceDomain.NewFromBigFloat(*gross, item.SinglePriceGross.Currency())
   271  
   272  		net := item.SinglePriceNet.Clone().Amount().Mul(item.SinglePriceNet.Amount(), big.NewFloat(float64(*itemUpdateCommand.Qty)))
   273  		itemDelivery.Cartitems[index].RowPriceNet = priceDomain.NewFromBigFloat(*net, item.SinglePriceNet.Currency())
   274  
   275  		itemDelivery.Cartitems[index].RowPriceGrossWithDiscount = itemDelivery.Cartitems[index].RowPriceGross
   276  		if rowPriceGrossWithDiscount, err := itemDelivery.Cartitems[index].RowPriceGross.Sub(itemDelivery.Cartitems[index].TotalDiscountAmount); err == nil {
   277  			itemDelivery.Cartitems[index].RowPriceGrossWithDiscount = rowPriceGrossWithDiscount
   278  		}
   279  
   280  		itemDelivery.Cartitems[index].RowPriceNetWithDiscount = itemDelivery.Cartitems[index].RowPriceNet
   281  		if rowPriceNetWithDiscount, err := itemDelivery.Cartitems[index].RowPriceNet.Sub(itemDelivery.Cartitems[index].TotalDiscountAmount); err == nil {
   282  			itemDelivery.Cartitems[index].RowPriceNetWithDiscount = rowPriceNetWithDiscount
   283  		}
   284  
   285  		itemDelivery.Cartitems[index].RowPriceGrossWithItemRelatedDiscount = itemDelivery.Cartitems[index].RowPriceGross
   286  		if rowPriceGrossWithItemRelatedDiscount, err := itemDelivery.Cartitems[index].RowPriceGross.Sub(itemDelivery.Cartitems[index].ItemRelatedDiscountAmount); err == nil {
   287  			itemDelivery.Cartitems[index].RowPriceGrossWithItemRelatedDiscount = rowPriceGrossWithItemRelatedDiscount
   288  		}
   289  
   290  		itemDelivery.Cartitems[index].RowPriceNetWithItemRelatedDiscount = itemDelivery.Cartitems[index].RowPriceNet
   291  		if rowPriceNetWithItemRelatedDiscount, err := itemDelivery.Cartitems[index].RowPriceNet.Sub(itemDelivery.Cartitems[index].ItemRelatedDiscountAmount); err == nil {
   292  			itemDelivery.Cartitems[index].RowPriceNetWithItemRelatedDiscount = rowPriceNetWithItemRelatedDiscount
   293  		}
   294  
   295  		if cob.defaultTaxRate > 0.0 {
   296  			taxAmount, err := itemDelivery.Cartitems[index].RowPriceGross.Sub(itemDelivery.Cartitems[index].RowPriceNet)
   297  			if err != nil {
   298  				return fmt.Errorf("DefaultCartBehaviour: error calculating tax amount: %w", err)
   299  			}
   300  
   301  			itemDelivery.Cartitems[index].RowTaxes[0].Amount = taxAmount
   302  		}
   303  	}
   304  
   305  	// update the delivery with the new info
   306  	for index, delivery := range cart.Deliveries {
   307  		if itemDelivery.DeliveryInfo.Code == delivery.DeliveryInfo.Code {
   308  			cart.Deliveries[index] = *itemDelivery
   309  		}
   310  	}
   311  
   312  	return nil
   313  }
   314  
   315  // AddToCart add an item to the cart
   316  func (cob *DefaultCartBehaviour) AddToCart(ctx context.Context, cart *domaincart.Cart, deliveryCode string, addRequest domaincart.AddRequest) (*domaincart.Cart, domaincart.DeferEvents, error) {
   317  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/AddToCart")
   318  	defer span.End()
   319  
   320  	if cart != nil && !cob.cartStorage.HasCart(ctx, cart.ID) {
   321  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: %w for cart id %q during add", domaincart.ErrCartNotFound, cart.ID)
   322  	}
   323  
   324  	newCart, err := cart.Clone()
   325  	if err != nil {
   326  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   327  	}
   328  
   329  	// create delivery if it does not yet exist
   330  	if !newCart.HasDeliveryForCode(deliveryCode) {
   331  		// create delivery and add item
   332  		delivery := new(domaincart.Delivery)
   333  		delivery.DeliveryInfo.Code = deliveryCode
   334  		newCart.Deliveries = append(newCart.Deliveries, *delivery)
   335  	}
   336  
   337  	delivery, err := cob.addToDelivery(ctx, newCart.GetDeliveryByCodeWithoutBool(deliveryCode), addRequest)
   338  	if err != nil {
   339  		return nil, nil, err
   340  	}
   341  
   342  	for k, del := range newCart.Deliveries {
   343  		if del.DeliveryInfo.Code == delivery.DeliveryInfo.Code {
   344  			newCart.Deliveries[k] = *delivery
   345  		}
   346  	}
   347  
   348  	err = cob.collectTotals(&newCart)
   349  	if err != nil {
   350  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   351  	}
   352  
   353  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   354  	if err != nil {
   355  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   356  	}
   357  
   358  	return cob.resetPaymentSelectionIfInvalid(ctx, &newCart)
   359  }
   360  
   361  // has cart current delivery, check if there is an item present for this delivery
   362  func (cob *DefaultCartBehaviour) addToDelivery(ctx context.Context, delivery *domaincart.Delivery, addRequest domaincart.AddRequest) (*domaincart.Delivery, error) {
   363  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/addToDelivery")
   364  	defer span.End()
   365  
   366  	for index, item := range delivery.Cartitems {
   367  		if item.MarketplaceCode != addRequest.MarketplaceCode {
   368  			continue
   369  		}
   370  
   371  		if item.VariantMarketPlaceCode != addRequest.VariantMarketplaceCode {
   372  			continue
   373  		}
   374  
   375  		if !item.BundleConfig.Equals(addRequest.BundleConfiguration) {
   376  			continue
   377  		}
   378  
   379  		addRequest.Qty += item.Qty
   380  
   381  		if addRequest.AdditionalData == nil && len(item.AdditionalData) > 0 {
   382  			addRequest.AdditionalData = make(map[string]string)
   383  		}
   384  
   385  		// copy additional data
   386  		for key, val := range item.AdditionalData {
   387  			addRequest.AdditionalData[key] = val
   388  		}
   389  
   390  		// create and add new item
   391  		cartItem, err := cob.buildItemForCart(ctx, addRequest)
   392  		if err != nil {
   393  			return nil, err
   394  		}
   395  
   396  		delivery.Cartitems[index] = *cartItem
   397  
   398  		return delivery, nil
   399  	}
   400  
   401  	ctx = decorator.ContextWithDeliveryCode(ctx, delivery.DeliveryInfo.Code)
   402  
   403  	// create and add new item
   404  	cartItem, err := cob.buildItemForCart(ctx, addRequest)
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  
   409  	delivery.Cartitems = append(delivery.Cartitems, *cartItem)
   410  
   411  	return delivery, nil
   412  }
   413  
   414  func (cob *DefaultCartBehaviour) buildItemForCart(ctx context.Context, addRequest domaincart.AddRequest) (*domaincart.Item, error) {
   415  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/buildItemForCart")
   416  	defer span.End()
   417  
   418  	// create and add new item
   419  	product, err := cob.productService.Get(ctx, addRequest.MarketplaceCode)
   420  	if err != nil {
   421  		return nil, fmt.Errorf("error getting product: %w", err)
   422  	}
   423  
   424  	// Get variant of configurable product
   425  	if configurableProduct, ok := product.(domain.ConfigurableProduct); ok && addRequest.VariantMarketplaceCode != "" {
   426  		productWithActiveVariant, err := configurableProduct.GetConfigurableWithActiveVariant(addRequest.VariantMarketplaceCode)
   427  		if err != nil {
   428  			return nil, fmt.Errorf("error getting configurable with active variant: %w", err)
   429  		}
   430  
   431  		product = productWithActiveVariant
   432  	}
   433  
   434  	if bundleProduct, ok := product.(domain.BundleProduct); ok && len(addRequest.BundleConfiguration) != 0 {
   435  		bundleConfig := addRequest.BundleConfiguration
   436  
   437  		bundleProductWithActiveChoices, err := bundleProduct.GetBundleProductWithActiveChoices(bundleConfig)
   438  		if err != nil {
   439  			return nil, fmt.Errorf("error getting bundle with active choices: %w", err)
   440  		}
   441  
   442  		product = bundleProductWithActiveChoices
   443  	}
   444  
   445  	return cob.createCartItemFromProduct(addRequest.Qty, addRequest.MarketplaceCode, addRequest.VariantMarketplaceCode, addRequest.AdditionalData, addRequest.BundleConfiguration, product)
   446  }
   447  func (cob *DefaultCartBehaviour) createCartItemFromProduct(qty int, marketplaceCode string, variantMarketPlaceCode string,
   448  	additonalData map[string]string, bundleConfig domain.BundleConfiguration, product domain.BasicProduct) (*domaincart.Item, error) {
   449  	item := &domaincart.Item{
   450  		ID:                     strconv.Itoa(rand.Int()),
   451  		ExternalReference:      strconv.Itoa(rand.Int()),
   452  		MarketplaceCode:        marketplaceCode,
   453  		VariantMarketPlaceCode: variantMarketPlaceCode,
   454  		ProductName:            product.BaseData().Title,
   455  		Qty:                    qty,
   456  		AdditionalData:         additonalData,
   457  	}
   458  
   459  	currency := product.SaleableData().ActivePrice.GetFinalPrice().Currency()
   460  
   461  	if cob.grossPricing {
   462  		item.SinglePriceGross = product.SaleableData().ActivePrice.GetFinalPrice().GetPayable()
   463  		net := item.SinglePriceGross.Clone().Amount().Quo(item.SinglePriceGross.Amount(), big.NewFloat(1+(cob.defaultTaxRate/100)))
   464  		item.SinglePriceNet = priceDomain.NewFromBigFloat(*net, currency).GetPayable()
   465  	} else {
   466  		item.SinglePriceNet = product.SaleableData().ActivePrice.GetFinalPrice().GetPayable()
   467  		gross := item.SinglePriceGross.Clone().Amount().Mul(item.SinglePriceNet.Amount(), big.NewFloat(1+(cob.defaultTaxRate/100)))
   468  		item.SinglePriceGross = priceDomain.NewFromBigFloat(*gross, currency).GetPayable()
   469  	}
   470  
   471  	gross := item.SinglePriceGross.Clone().Amount().Mul(item.SinglePriceGross.Amount(), big.NewFloat(float64(qty)))
   472  	item.RowPriceGross = priceDomain.NewFromBigFloat(*gross, currency)
   473  	_ = item.RowPriceGross.FloatAmount()
   474  	net := item.SinglePriceNet.Clone().Amount().Mul(item.SinglePriceNet.Amount(), big.NewFloat(float64(qty)))
   475  	item.RowPriceNet = priceDomain.NewFromBigFloat(*net, currency)
   476  	_ = item.RowPriceNet.FloatAmount()
   477  
   478  	item.RowPriceGrossWithDiscount, item.RowPriceNetWithDiscount = item.RowPriceGross, item.RowPriceNet
   479  	item.RowPriceGrossWithItemRelatedDiscount, item.RowPriceNetWithItemRelatedDiscount = item.RowPriceGross, item.RowPriceNet
   480  
   481  	if cob.defaultTaxRate > 0.0 {
   482  		taxAmount, err := item.RowPriceGross.Sub(item.RowPriceNet)
   483  		if err != nil {
   484  			return nil, fmt.Errorf("DefaultCartBehaviour: error calculating tax amount: %w", err)
   485  		}
   486  
   487  		item.RowTaxes = []domaincart.Tax{{
   488  			Amount: taxAmount,
   489  			Type:   "default",
   490  			Rate:   big.NewFloat(cob.defaultTaxRate),
   491  		}}
   492  	}
   493  
   494  	item.TotalDiscountAmount = priceDomain.NewZero(currency)
   495  	item.ItemRelatedDiscountAmount = priceDomain.NewZero(currency)
   496  	item.NonItemRelatedDiscountAmount = priceDomain.NewZero(currency)
   497  
   498  	item.BundleConfig = bundleConfig
   499  
   500  	return item, nil
   501  }
   502  
   503  // CleanCart removes everything from the cart, e.g. deliveries, billing address, etc
   504  func (cob *DefaultCartBehaviour) CleanCart(ctx context.Context, cart *domaincart.Cart) (*domaincart.Cart, domaincart.DeferEvents, error) {
   505  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/CleanCart")
   506  	defer span.End()
   507  
   508  	if !cob.cartStorage.HasCart(ctx, cart.ID) {
   509  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: %w for cart id %q during clean cart", domaincart.ErrCartNotFound, cart.ID)
   510  	}
   511  
   512  	newCart, err := cart.Clone()
   513  	if err != nil {
   514  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   515  	}
   516  
   517  	newCart.Deliveries = []domaincart.Delivery{}
   518  	newCart.AppliedCouponCodes = nil
   519  	newCart.AppliedGiftCards = nil
   520  	newCart.PaymentSelection = nil
   521  	newCart.AdditionalData = domaincart.AdditionalData{}
   522  	newCart.Purchaser = nil
   523  	newCart.BillingAddress = nil
   524  	newCart.Totalitems = nil
   525  
   526  	err = cob.collectTotals(&newCart)
   527  	if err != nil {
   528  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   529  	}
   530  
   531  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   532  	if err != nil {
   533  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   534  	}
   535  
   536  	return &newCart, nil, nil
   537  }
   538  
   539  // CleanDelivery removes a complete delivery with its items from the cart
   540  func (cob *DefaultCartBehaviour) CleanDelivery(ctx context.Context, cart *domaincart.Cart, deliveryCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   541  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/CleanDelivery")
   542  	defer span.End()
   543  
   544  	if !cob.cartStorage.HasCart(ctx, cart.ID) {
   545  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: %w for cart id %q during clean delivery", domaincart.ErrCartNotFound, cart.ID)
   546  	}
   547  
   548  	newCart, err := cart.Clone()
   549  	if err != nil {
   550  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   551  	}
   552  
   553  	// create delivery if it does not yet exist
   554  	if !newCart.HasDeliveryForCode(deliveryCode) {
   555  		return nil, nil, errors.Errorf("DefaultCartBehaviour: delivery %s not found", deliveryCode)
   556  	}
   557  
   558  	var position int
   559  
   560  	for index, delivery := range newCart.Deliveries {
   561  		if delivery.DeliveryInfo.Code == deliveryCode {
   562  			position = index
   563  			break
   564  		}
   565  	}
   566  
   567  	newLength := len(newCart.Deliveries) - 1
   568  	newCart.Deliveries[position] = newCart.Deliveries[newLength]
   569  	newCart.Deliveries[newLength] = domaincart.Delivery{}
   570  	newCart.Deliveries = newCart.Deliveries[:newLength]
   571  
   572  	err = cob.collectTotals(&newCart)
   573  	if err != nil {
   574  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   575  	}
   576  
   577  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   578  	if err != nil {
   579  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   580  	}
   581  
   582  	return cob.resetPaymentSelectionIfInvalid(ctx, &newCart)
   583  }
   584  
   585  // UpdatePurchaser - updates purchaser
   586  func (cob *DefaultCartBehaviour) UpdatePurchaser(ctx context.Context, cart *domaincart.Cart, purchaser *domaincart.Person, additionalData *domaincart.AdditionalData) (*domaincart.Cart, domaincart.DeferEvents, error) {
   587  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdatePurchaser")
   588  	defer span.End()
   589  
   590  	newCart, err := cart.Clone()
   591  	if err != nil {
   592  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   593  	}
   594  
   595  	newCart.Purchaser = purchaser
   596  
   597  	if additionalData != nil {
   598  		if newCart.AdditionalData.CustomAttributes == nil {
   599  			newCart.AdditionalData.CustomAttributes = make(map[string]string)
   600  		}
   601  
   602  		for key, val := range additionalData.CustomAttributes {
   603  			newCart.AdditionalData.CustomAttributes[key] = val
   604  		}
   605  	}
   606  
   607  	err = cob.collectTotals(&newCart)
   608  	if err != nil {
   609  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   610  	}
   611  
   612  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   613  	if err != nil {
   614  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   615  	}
   616  
   617  	return &newCart, nil, nil
   618  }
   619  
   620  // UpdateBillingAddress - updates address
   621  func (cob *DefaultCartBehaviour) UpdateBillingAddress(ctx context.Context, cart *domaincart.Cart, billingAddress domaincart.Address) (*domaincart.Cart, domaincart.DeferEvents, error) {
   622  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdateBillingAddress")
   623  	defer span.End()
   624  
   625  	newCart, err := cart.Clone()
   626  	if err != nil {
   627  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   628  	}
   629  
   630  	newCart.BillingAddress = &billingAddress
   631  
   632  	err = cob.collectTotals(&newCart)
   633  	if err != nil {
   634  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   635  	}
   636  
   637  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   638  	if err != nil {
   639  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   640  	}
   641  
   642  	return &newCart, nil, nil
   643  }
   644  
   645  // UpdateAdditionalData updates additional data
   646  func (cob *DefaultCartBehaviour) UpdateAdditionalData(ctx context.Context, cart *domaincart.Cart, additionalData *domaincart.AdditionalData) (*domaincart.Cart, domaincart.DeferEvents, error) {
   647  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdateAdditionalData")
   648  	defer span.End()
   649  
   650  	newCart, err := cart.Clone()
   651  	if err != nil {
   652  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   653  	}
   654  
   655  	newCart.AdditionalData = *additionalData
   656  
   657  	err = cob.collectTotals(&newCart)
   658  	if err != nil {
   659  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   660  	}
   661  
   662  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   663  	if err != nil {
   664  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error updating additional data: %w", err)
   665  	}
   666  
   667  	return &newCart, nil, nil
   668  }
   669  
   670  // UpdatePaymentSelection updates payment on cart
   671  func (cob *DefaultCartBehaviour) UpdatePaymentSelection(ctx context.Context, cart *domaincart.Cart, paymentSelection domaincart.PaymentSelection) (*domaincart.Cart, domaincart.DeferEvents, error) {
   672  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdatePaymentSelection")
   673  	defer span.End()
   674  
   675  	newCart, err := cart.Clone()
   676  	if err != nil {
   677  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   678  	}
   679  
   680  	if paymentSelection != nil {
   681  		err := cob.checkPaymentSelection(ctx, &newCart, paymentSelection)
   682  		if err != nil {
   683  			return nil, nil, err
   684  		}
   685  	}
   686  
   687  	newCart.PaymentSelection = paymentSelection
   688  
   689  	err = cob.collectTotals(&newCart)
   690  	if err != nil {
   691  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   692  	}
   693  
   694  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   695  	if err != nil {
   696  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   697  	}
   698  
   699  	return &newCart, nil, nil
   700  }
   701  
   702  // UpdateDeliveryInfo updates a delivery info
   703  func (cob *DefaultCartBehaviour) UpdateDeliveryInfo(ctx context.Context, cart *domaincart.Cart, deliveryCode string, deliveryInfoUpdateCommand domaincart.DeliveryInfoUpdateCommand) (*domaincart.Cart, domaincart.DeferEvents, error) {
   704  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdateDeliveryInfo")
   705  	defer span.End()
   706  
   707  	newCart, err := cart.Clone()
   708  	if err != nil {
   709  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   710  	}
   711  
   712  	deliveryInfo := deliveryInfoUpdateCommand.DeliveryInfo
   713  	deliveryInfo.AdditionalDeliveryInfos = deliveryInfoUpdateCommand.Additional()
   714  
   715  	for key, delivery := range newCart.Deliveries {
   716  		if delivery.DeliveryInfo.Code == deliveryCode {
   717  			newCart.Deliveries[key].DeliveryInfo = deliveryInfo
   718  
   719  			err = cob.collectTotals(&newCart)
   720  			if err != nil {
   721  				return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   722  			}
   723  
   724  			err := cob.cartStorage.StoreCart(ctx, &newCart)
   725  			if err != nil {
   726  				return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   727  			}
   728  
   729  			return &newCart, nil, nil
   730  		}
   731  	}
   732  
   733  	newCart.Deliveries = append(newCart.Deliveries, domaincart.Delivery{DeliveryInfo: deliveryInfo})
   734  
   735  	err = cob.collectTotals(&newCart)
   736  	if err != nil {
   737  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   738  	}
   739  
   740  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   741  	if err != nil {
   742  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   743  	}
   744  
   745  	return cob.resetPaymentSelectionIfInvalid(ctx, &newCart)
   746  }
   747  
   748  // UpdateDeliveryInfoAdditionalData @todo implement when needed
   749  func (cob *DefaultCartBehaviour) UpdateDeliveryInfoAdditionalData(ctx context.Context, cart *domaincart.Cart, deliveryCode string, additionalData *domaincart.AdditionalData) (*domaincart.Cart, domaincart.DeferEvents, error) {
   750  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/UpdateDeliveryInfoAdditionalData")
   751  	defer span.End()
   752  
   753  	return cart, nil, nil
   754  }
   755  
   756  // GetCart returns the current cart from storage
   757  func (cob *DefaultCartBehaviour) GetCart(ctx context.Context, cartID string) (*domaincart.Cart, error) {
   758  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/GetCart")
   759  	defer span.End()
   760  
   761  	if !cob.cartStorage.HasCart(ctx, cartID) {
   762  		err := fmt.Errorf("DefaultCartBehaviour: %w for cart id %q during get", domaincart.ErrCartNotFound, cartID)
   763  		cob.logger.WithField(flamingo.LogKeyCategory, logCategory).Info(err)
   764  
   765  		return nil, err
   766  	}
   767  
   768  	cart, err := cob.cartStorage.GetCart(ctx, cartID)
   769  	if err != nil {
   770  		cob.logger.WithField(flamingo.LogKeyCategory, logCategory).Info(fmt.Errorf("DefaultCartBehaviour: get cart from storage: %w ", err))
   771  		return nil, domaincart.ErrCartNotFound
   772  	}
   773  
   774  	newCart, err := cart.Clone()
   775  	if err != nil {
   776  		cob.logger.WithField(flamingo.LogKeyCategory, logCategory).Info(fmt.Errorf("DefaultCartBehaviour: cart clone failed: %w ", err))
   777  		return nil, domaincart.ErrCartNotFound
   778  	}
   779  
   780  	return &newCart, nil
   781  }
   782  
   783  // StoreNewCart created and stores a new cart.
   784  func (cob *DefaultCartBehaviour) StoreNewCart(ctx context.Context, cart *domaincart.Cart) (*domaincart.Cart, error) {
   785  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/StoreNewCart")
   786  	defer span.End()
   787  
   788  	if cart.ID == "" {
   789  		return nil, errors.New("no id given")
   790  	}
   791  
   792  	newCart, err := cart.Clone()
   793  	if err != nil {
   794  		return nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   795  	}
   796  
   797  	newCart.DefaultCurrency = cob.defaultCurrency
   798  
   799  	err = cob.collectTotals(&newCart)
   800  	if err != nil {
   801  		return nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   802  	}
   803  
   804  	err = cob.cartStorage.StoreCart(ctx, &newCart)
   805  	if err != nil {
   806  		return nil, fmt.Errorf("DefaultCartBehaviour: error saving cart: %w", err)
   807  	}
   808  
   809  	return &newCart, nil
   810  }
   811  
   812  // ApplyVoucher applies a voucher to the cart
   813  func (cob *DefaultCartBehaviour) ApplyVoucher(ctx context.Context, cart *domaincart.Cart, couponCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   814  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/ApplyVoucher")
   815  	defer span.End()
   816  
   817  	newCart, err := cart.Clone()
   818  	if err != nil {
   819  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   820  	}
   821  
   822  	newCartWithVoucher, err := cob.voucherHandler.ApplyVoucher(ctx, &newCart, couponCode)
   823  	if err != nil {
   824  		return nil, nil, err
   825  	}
   826  
   827  	err = cob.collectTotals(newCartWithVoucher)
   828  	if err != nil {
   829  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   830  	}
   831  
   832  	err = cob.cartStorage.StoreCart(ctx, newCartWithVoucher)
   833  	if err != nil {
   834  		return nil, nil, err
   835  	}
   836  
   837  	return cob.resetPaymentSelectionIfInvalid(ctx, newCartWithVoucher)
   838  }
   839  
   840  // ApplyAny applies a voucher or giftcard to the cart
   841  func (cob *DefaultCartBehaviour) ApplyAny(ctx context.Context, cart *domaincart.Cart, anyCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   842  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/ApplyAny")
   843  	defer span.End()
   844  
   845  	currentCart, deferFunc, err := cob.ApplyVoucher(ctx, cart, anyCode)
   846  	if err == nil {
   847  		// successfully applied as voucher
   848  		return currentCart, deferFunc, nil
   849  	}
   850  
   851  	// some error occurred, retry as giftcard
   852  	return cob.ApplyGiftCard(ctx, cart, anyCode)
   853  }
   854  
   855  // RemoveVoucher removes a voucher from the cart
   856  func (cob *DefaultCartBehaviour) RemoveVoucher(ctx context.Context, cart *domaincart.Cart, couponCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   857  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/RemoveVoucher")
   858  	defer span.End()
   859  
   860  	newCart, err := cart.Clone()
   861  	if err != nil {
   862  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   863  	}
   864  
   865  	newCartWithoutVoucher, err := cob.voucherHandler.RemoveVoucher(ctx, &newCart, couponCode)
   866  	if err != nil {
   867  		return nil, nil, err
   868  	}
   869  
   870  	err = cob.collectTotals(newCartWithoutVoucher)
   871  	if err != nil {
   872  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   873  	}
   874  
   875  	err = cob.cartStorage.StoreCart(ctx, newCartWithoutVoucher)
   876  	if err != nil {
   877  		return nil, nil, err
   878  	}
   879  
   880  	return cob.resetPaymentSelectionIfInvalid(ctx, newCartWithoutVoucher)
   881  }
   882  
   883  // ApplyGiftCard applies a gift card to the cart
   884  // if a GiftCard is applied, it will be added to the array AppliedGiftCards on the cart
   885  func (cob *DefaultCartBehaviour) ApplyGiftCard(ctx context.Context, cart *domaincart.Cart, giftCardCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   886  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/ApplyGiftCard")
   887  	defer span.End()
   888  
   889  	newCart, err := cart.Clone()
   890  	if err != nil {
   891  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   892  	}
   893  
   894  	newCartWithGiftCard, err := cob.giftCardHandler.ApplyGiftCard(ctx, &newCart, giftCardCode)
   895  	if err != nil {
   896  		return nil, nil, err
   897  	}
   898  
   899  	err = cob.collectTotals(newCartWithGiftCard)
   900  	if err != nil {
   901  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   902  	}
   903  
   904  	err = cob.cartStorage.StoreCart(ctx, newCartWithGiftCard)
   905  	if err != nil {
   906  		return nil, nil, err
   907  	}
   908  
   909  	return cob.resetPaymentSelectionIfInvalid(ctx, newCartWithGiftCard)
   910  }
   911  
   912  // RemoveGiftCard removes a gift card from the cart
   913  // if a GiftCard is removed, it will be removed from the array AppliedGiftCards on the cart
   914  func (cob *DefaultCartBehaviour) RemoveGiftCard(ctx context.Context, cart *domaincart.Cart, giftCardCode string) (*domaincart.Cart, domaincart.DeferEvents, error) {
   915  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/RemoveGiftCard")
   916  	defer span.End()
   917  
   918  	newCart, err := cart.Clone()
   919  	if err != nil {
   920  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error cloning cart: %w", err)
   921  	}
   922  
   923  	newCartWithOutGiftCard, err := cob.giftCardHandler.RemoveGiftCard(ctx, &newCart, giftCardCode)
   924  	if err != nil {
   925  		return nil, nil, err
   926  	}
   927  
   928  	err = cob.collectTotals(newCartWithOutGiftCard)
   929  	if err != nil {
   930  		return nil, nil, fmt.Errorf("DefaultCartBehaviour: error collecting totals: %w", err)
   931  	}
   932  
   933  	err = cob.cartStorage.StoreCart(ctx, newCartWithOutGiftCard)
   934  	if err != nil {
   935  		return nil, nil, err
   936  	}
   937  
   938  	return cob.resetPaymentSelectionIfInvalid(ctx, newCartWithOutGiftCard)
   939  }
   940  
   941  // isPaymentSelectionValid checks if the grand total of the cart matches the total of the supplied payment selection
   942  func (cob *DefaultCartBehaviour) checkPaymentSelection(ctx context.Context, cart *domaincart.Cart, paymentSelection domaincart.PaymentSelection) error {
   943  	_, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/checkPaymentSelection")
   944  	defer span.End()
   945  
   946  	if paymentSelection == nil {
   947  		return nil
   948  	}
   949  
   950  	paymentSelectionTotal := paymentSelection.TotalValue()
   951  
   952  	if !cart.GrandTotal.LikelyEqual(paymentSelectionTotal) {
   953  		return errors.New("Payment Total does not match with Grandtotal")
   954  	}
   955  
   956  	return nil
   957  }
   958  
   959  // resetPaymentSelectionIfInvalid checks for valid paymentselection on given cart and deletes in case it is invalid
   960  func (cob *DefaultCartBehaviour) resetPaymentSelectionIfInvalid(ctx context.Context, cart *domaincart.Cart) (*domaincart.Cart, domaincart.DeferEvents, error) {
   961  	ctx, span := trace.StartSpan(ctx, "cart/DefaultCartBehaviour/resetPaymentSelectionIfInvalid")
   962  	defer span.End()
   963  
   964  	if cart.PaymentSelection == nil {
   965  		return cart, nil, nil
   966  	}
   967  
   968  	err := cob.checkPaymentSelection(ctx, cart, cart.PaymentSelection)
   969  	if err != nil {
   970  		cart, defers, err := cob.UpdatePaymentSelection(ctx, cart, nil)
   971  		defers = append(defers, &events.PaymentSelectionHasBeenResetEvent{Cart: cart})
   972  
   973  		return cart, defers, err
   974  	}
   975  
   976  	return cart, nil, nil
   977  }
   978  
   979  //nolint:cyclop // collecting total this way is more explicit
   980  func (cob *DefaultCartBehaviour) collectTotals(cart *domaincart.Cart) error {
   981  	var err error
   982  
   983  	cart.TotalGiftCardAmount = priceDomain.NewZero(cart.DefaultCurrency)
   984  	cart.GrandTotalWithGiftCards = priceDomain.NewZero(cart.DefaultCurrency)
   985  	cart.GrandTotal = priceDomain.NewZero(cart.DefaultCurrency)
   986  	cart.GrandTotalNet = priceDomain.NewZero(cart.DefaultCurrency)
   987  	cart.GrandTotalNetWithGiftCards = priceDomain.NewZero(cart.DefaultCurrency)
   988  	cart.ShippingNet = priceDomain.NewZero(cart.DefaultCurrency)
   989  	cart.ShippingNetWithDiscounts = priceDomain.NewZero(cart.DefaultCurrency)
   990  	cart.ShippingGross = priceDomain.NewZero(cart.DefaultCurrency)
   991  	cart.ShippingGrossWithDiscounts = priceDomain.NewZero(cart.DefaultCurrency)
   992  	cart.SubTotalGross = priceDomain.NewZero(cart.DefaultCurrency)
   993  	cart.SubTotalNet = priceDomain.NewZero(cart.DefaultCurrency)
   994  	cart.SubTotalGrossWithDiscounts = priceDomain.NewZero(cart.DefaultCurrency)
   995  	cart.SubTotalNetWithDiscounts = priceDomain.NewZero(cart.DefaultCurrency)
   996  	cart.TotalDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
   997  	cart.NonItemRelatedDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
   998  	cart.ItemRelatedDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
   999  
  1000  	for i := 0; i < len(cart.Deliveries); i++ {
  1001  		delivery := &cart.Deliveries[i]
  1002  		delivery.SubTotalGross = priceDomain.NewZero(cart.DefaultCurrency)
  1003  		delivery.SubTotalNet = priceDomain.NewZero(cart.DefaultCurrency)
  1004  		delivery.TotalDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
  1005  		delivery.SubTotalDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
  1006  		delivery.NonItemRelatedDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
  1007  		delivery.ItemRelatedDiscountAmount = priceDomain.NewZero(cart.DefaultCurrency)
  1008  		delivery.SubTotalGrossWithDiscounts = priceDomain.NewZero(cart.DefaultCurrency)
  1009  		delivery.SubTotalNetWithDiscounts = priceDomain.NewZero(cart.DefaultCurrency)
  1010  		delivery.GrandTotal = priceDomain.NewZero(cart.DefaultCurrency)
  1011  
  1012  		if !delivery.ShippingItem.PriceGrossWithDiscounts.IsZero() {
  1013  			delivery.GrandTotal = delivery.GrandTotal.ForceAdd(delivery.ShippingItem.PriceGrossWithDiscounts)
  1014  
  1015  			discounts, err := delivery.ShippingItem.AppliedDiscounts.Sum()
  1016  			if err != nil {
  1017  				return fmt.Errorf("failed to sum discounts: %w", err)
  1018  			}
  1019  
  1020  			delivery.TotalDiscountAmount = delivery.TotalDiscountAmount.ForceAdd(discounts)
  1021  		}
  1022  
  1023  		for _, cartitem := range delivery.Cartitems {
  1024  			delivery.SubTotalGross = delivery.SubTotalGross.ForceAdd(cartitem.RowPriceGross)
  1025  			delivery.SubTotalNet = delivery.SubTotalNet.ForceAdd(cartitem.RowPriceNet)
  1026  			delivery.TotalDiscountAmount = delivery.TotalDiscountAmount.ForceAdd(cartitem.TotalDiscountAmount)
  1027  			delivery.SubTotalDiscountAmount = delivery.SubTotalDiscountAmount.ForceAdd(cartitem.TotalDiscountAmount)
  1028  			delivery.NonItemRelatedDiscountAmount = delivery.NonItemRelatedDiscountAmount.ForceAdd(cartitem.NonItemRelatedDiscountAmount)
  1029  			delivery.ItemRelatedDiscountAmount = delivery.ItemRelatedDiscountAmount.ForceAdd(cartitem.ItemRelatedDiscountAmount)
  1030  			delivery.SubTotalGrossWithDiscounts = delivery.SubTotalGrossWithDiscounts.ForceAdd(cartitem.RowPriceGrossWithDiscount)
  1031  			delivery.SubTotalNetWithDiscounts = delivery.SubTotalNetWithDiscounts.ForceAdd(cartitem.RowPriceNetWithDiscount)
  1032  			delivery.GrandTotal = delivery.GrandTotal.ForceAdd(cartitem.RowPriceGrossWithDiscount)
  1033  		}
  1034  
  1035  		cart.GrandTotal = cart.GrandTotal.ForceAdd(delivery.GrandTotal)
  1036  		cart.GrandTotalNet = cart.GrandTotalNet.ForceAdd(delivery.SubTotalNetWithDiscounts).ForceAdd(delivery.ShippingItem.PriceNetWithDiscounts)
  1037  		cart.ShippingNet = cart.ShippingNet.ForceAdd(delivery.ShippingItem.PriceNet)
  1038  		cart.ShippingNetWithDiscounts = cart.ShippingNetWithDiscounts.ForceAdd(delivery.ShippingItem.PriceNetWithDiscounts)
  1039  		cart.ShippingGross = cart.ShippingGross.ForceAdd(delivery.ShippingItem.PriceGross)
  1040  		cart.ShippingGrossWithDiscounts = cart.ShippingGrossWithDiscounts.ForceAdd(delivery.ShippingItem.PriceGrossWithDiscounts)
  1041  		cart.SubTotalGross = cart.SubTotalGross.ForceAdd(delivery.SubTotalGross)
  1042  		cart.SubTotalNet = cart.SubTotalNet.ForceAdd(delivery.SubTotalNet)
  1043  		cart.SubTotalGrossWithDiscounts = cart.SubTotalGrossWithDiscounts.ForceAdd(delivery.SubTotalGrossWithDiscounts)
  1044  		cart.SubTotalNetWithDiscounts = cart.SubTotalNetWithDiscounts.ForceAdd(delivery.SubTotalNetWithDiscounts)
  1045  		cart.TotalDiscountAmount = cart.TotalDiscountAmount.ForceAdd(delivery.TotalDiscountAmount)
  1046  		cart.NonItemRelatedDiscountAmount = cart.NonItemRelatedDiscountAmount.ForceAdd(delivery.NonItemRelatedDiscountAmount)
  1047  		cart.ItemRelatedDiscountAmount = cart.ItemRelatedDiscountAmount.ForceAdd(delivery.ItemRelatedDiscountAmount)
  1048  	}
  1049  
  1050  	for _, totalitem := range cart.Totalitems {
  1051  		cart.GrandTotal = cart.GrandTotal.ForceAdd(totalitem.Price)
  1052  	}
  1053  
  1054  	sumAppliedGiftCards := priceDomain.NewZero(cart.DefaultCurrency)
  1055  	for _, card := range cart.AppliedGiftCards {
  1056  		sumAppliedGiftCards = sumAppliedGiftCards.ForceAdd(card.Applied)
  1057  	}
  1058  
  1059  	cart.TotalGiftCardAmount = sumAppliedGiftCards
  1060  
  1061  	cart.GrandTotalWithGiftCards, err = cart.GrandTotal.Sub(cart.TotalGiftCardAmount)
  1062  	if err != nil {
  1063  		return fmt.Errorf("failed to calculate grand total with gift cards: %w", err)
  1064  	}
  1065  
  1066  	if cart.GrandTotalWithGiftCards.IsNegative() {
  1067  		cart.GrandTotalWithGiftCards = priceDomain.NewZero(cart.DefaultCurrency)
  1068  	}
  1069  
  1070  	cart.GrandTotalNetWithGiftCards, err = cart.GrandTotalNet.Sub(cart.TotalGiftCardAmount)
  1071  	if err != nil {
  1072  		return fmt.Errorf("failed to calculate grand total net with gift cards: %w", err)
  1073  	}
  1074  
  1075  	if cart.GrandTotalNetWithGiftCards.IsNegative() {
  1076  		cart.GrandTotalNetWithGiftCards = priceDomain.NewZero(cart.DefaultCurrency)
  1077  	}
  1078  
  1079  	return nil
  1080  }
  1081  
  1082  // ApplyVoucher is called when applying a voucher
  1083  func (DefaultVoucherHandler) ApplyVoucher(ctx context.Context, cart *domaincart.Cart, _ string) (*domaincart.Cart, error) {
  1084  	_, span := trace.StartSpan(ctx, "cart/DefaultGiftCardHandler/ApplyVoucher")
  1085  	defer span.End()
  1086  
  1087  	return cart, nil
  1088  }
  1089  
  1090  // RemoveVoucher is called when removing a voucher
  1091  func (DefaultVoucherHandler) RemoveVoucher(ctx context.Context, cart *domaincart.Cart, _ string) (*domaincart.Cart, error) {
  1092  	_, span := trace.StartSpan(ctx, "cart/DefaultGiftCardHandler/RemoveVoucher")
  1093  	defer span.End()
  1094  
  1095  	return cart, nil
  1096  }
  1097  
  1098  // ApplyGiftCard is called when applying a gift card
  1099  func (DefaultGiftCardHandler) ApplyGiftCard(ctx context.Context, cart *domaincart.Cart, _ string) (*domaincart.Cart, error) {
  1100  	_, span := trace.StartSpan(ctx, "cart/DefaultGiftCardHandler/ApplyGiftCard")
  1101  	defer span.End()
  1102  
  1103  	return cart, nil
  1104  }
  1105  
  1106  // RemoveGiftCard is called when removing a gift card
  1107  func (DefaultGiftCardHandler) RemoveGiftCard(ctx context.Context, cart *domaincart.Cart, _ string) (*domaincart.Cart, error) {
  1108  	_, span := trace.StartSpan(ctx, "cart/DefaultGiftCardHandler/RemoveGiftCard")
  1109  	defer span.End()
  1110  
  1111  	return cart, nil
  1112  }