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