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 }