flamingo.me/flamingo-commerce/v3@v3.11.0/cart/application/eventHandling.go (about) 1 package application 2 3 import ( 4 "context" 5 "fmt" 6 7 "go.opencensus.io/trace" 8 9 "flamingo.me/flamingo/v3/core/auth" 10 "flamingo.me/flamingo/v3/framework/flamingo" 11 "flamingo.me/flamingo/v3/framework/web" 12 13 cartDomain "flamingo.me/flamingo-commerce/v3/cart/domain/cart" 14 ) 15 16 //go:generate go run github.com/vektra/mockery/v2@v2.42.3 --name CartMerger --case snake 17 18 type ( 19 // EventReceiver handles events from other packages 20 EventReceiver struct { 21 logger flamingo.Logger 22 cartReceiverService Receiver 23 cartCache CartCache 24 eventRouter flamingo.EventRouter 25 cartMerger CartMerger 26 } 27 28 CartMerger interface { 29 Merge(ctx context.Context, session *web.Session, guestCart cartDomain.Cart, customerCart cartDomain.Cart) 30 } 31 32 CartMergeStrategyMerge struct { 33 cartService Service 34 logger flamingo.Logger 35 } 36 37 CartMergeStrategyReplace struct { 38 cartService Service 39 logger flamingo.Logger 40 } 41 42 CartMergeStrategyNone struct{} 43 44 // PreCartMergeEvent is dispatched after getting the (current) guest cart and the customer cart before merging 45 PreCartMergeEvent struct { 46 GuestCart cartDomain.Cart 47 CustomerCart cartDomain.Cart 48 } 49 50 // PostCartMergeEvent is dispatched after merging the guest cart and the customer cart 51 PostCartMergeEvent struct { 52 MergedCart cartDomain.Cart 53 } 54 ) 55 56 var _ CartMerger = &CartMergeStrategyReplace{} 57 var _ CartMerger = &CartMergeStrategyMerge{} 58 var _ CartMerger = &CartMergeStrategyNone{} 59 60 // Inject dependencies 61 func (e *EventReceiver) Inject( 62 logger flamingo.Logger, 63 cartReceiverService Receiver, 64 eventRouter flamingo.EventRouter, 65 cartMerger CartMerger, 66 optionals *struct { 67 CartCache CartCache `inject:",optional"` 68 }, 69 ) { 70 e.logger = logger 71 e.cartReceiverService = cartReceiverService 72 e.eventRouter = eventRouter 73 e.cartMerger = cartMerger 74 75 if optionals != nil { 76 e.cartCache = optionals.CartCache 77 } 78 } 79 80 func (e *EventReceiver) prepareLogger(ctx context.Context) flamingo.Logger { 81 // do this on the fly to avoid wasting memory by holding a dedicated logger instance 82 return e.logger.WithField(flamingo.LogKeyCategory, "cart").WithField(flamingo.LogKeySubCategory, "cart-events").WithContext(ctx) 83 } 84 85 //nolint:cyclop // grabbing both the customer cart and the guest cart is a complex process 86 func (e *EventReceiver) handleLoginEvent(ctx context.Context, loginEvent *auth.WebLoginEvent) { 87 ctx, span := trace.StartSpan(ctx, "cart/EventReceiver/handleLoginEvent") 88 defer span.End() 89 90 if loginEvent == nil { 91 return 92 } 93 94 if loginEvent.Request == nil { 95 return 96 } 97 98 if loginEvent.Identity == nil { 99 return 100 } 101 102 session := loginEvent.Request.Session() 103 if !e.cartReceiverService.ShouldHaveGuestCart(session) { 104 return 105 } 106 107 guestCart, err := e.cartReceiverService.ViewGuestCart(ctx, session) 108 if err != nil { 109 e.prepareLogger(ctx).Error(fmt.Errorf("view guest cart failed: %w", err)) 110 return 111 } 112 113 customerCart, err := e.cartReceiverService.ViewCart(ctx, session) 114 if err != nil { 115 e.prepareLogger(ctx).Error(fmt.Errorf("view customer cart failed: %w", err)) 116 return 117 } 118 119 session.Delete(GuestCartSessionKey) 120 121 clonedGuestCart, _ := guestCart.Clone() 122 clonedCustomerCart, _ := customerCart.Clone() 123 e.eventRouter.Dispatch(ctx, &PreCartMergeEvent{GuestCart: clonedGuestCart, CustomerCart: clonedCustomerCart}) 124 125 // merge the cart depending on the set strategy 126 e.cartMerger.Merge(ctx, session, *guestCart, *customerCart) 127 128 if e.cartCache != nil { 129 cacheID, err := e.cartCache.BuildIdentifier(ctx, session) 130 if err == nil { 131 err = e.cartCache.Delete(ctx, session, cacheID) 132 if err != nil { 133 e.prepareLogger(ctx).Error(fmt.Errorf("can't delete cart cache entry %v: %w", cacheID, err)) 134 } 135 } 136 } 137 138 customerCart, err = e.cartReceiverService.ViewCart(ctx, session) 139 if err != nil { 140 e.prepareLogger(ctx).Error(fmt.Errorf("view customer cart failed: %w", err)) 141 return 142 } 143 144 mergedCart, _ := customerCart.Clone() 145 e.eventRouter.Dispatch(ctx, &PostCartMergeEvent{MergedCart: mergedCart}) 146 } 147 148 // Notify should get called by flamingo Eventlogic 149 func (e *EventReceiver) Notify(ctx context.Context, event flamingo.Event) { 150 ctx, span := trace.StartSpan(ctx, "cart/EventReceiver/Notify") 151 defer span.End() 152 153 switch currentEvent := event.(type) { 154 // Clean cart cache on logout 155 case *auth.WebLogoutEvent: 156 if e.cartCache != nil { 157 _ = e.cartCache.DeleteAll(ctx, currentEvent.Request.Session()) 158 } 159 // Handle WebLoginEvent and merge the cart 160 case *auth.WebLoginEvent: 161 web.RunWithDetachedContext(ctx, func(ctx context.Context) { 162 e.handleLoginEvent(ctx, currentEvent) 163 }) 164 // Clean the cart cache when the cart should be invalidated 165 case *cartDomain.InvalidateCartEvent: 166 if e.cartCache != nil { 167 cartID, err := e.cartCache.BuildIdentifier(ctx, currentEvent.Session) 168 if err == nil { 169 _ = e.cartCache.Invalidate(ctx, currentEvent.Session, cartID) 170 } 171 } 172 } 173 } 174 175 func (c *CartMergeStrategyNone) Merge(_ context.Context, _ *web.Session, _ cartDomain.Cart, _ cartDomain.Cart) { 176 // do nothing 177 } 178 179 // Inject dependencies 180 func (c *CartMergeStrategyReplace) Inject( 181 logger flamingo.Logger, 182 cartService Service, 183 ) *CartMergeStrategyReplace { 184 c.logger = logger 185 c.cartService = cartService 186 187 return c 188 } 189 190 //nolint:cyclop // setting all cart attributes is a complex task 191 func (c *CartMergeStrategyReplace) Merge(ctx context.Context, session *web.Session, guestCart cartDomain.Cart, _ cartDomain.Cart) { 192 var err error 193 194 c.logger.WithContext(ctx).Info("cleaning existing customer cart, to be able to replace the content with the guest one.") 195 196 err = c.cartService.Clean(ctx, session) 197 if err != nil { 198 c.logger.WithContext(ctx).Error(fmt.Errorf("cleaning the customer cart didn't work: %w", err)) 199 } 200 201 for _, delivery := range guestCart.Deliveries { 202 err = c.cartService.UpdateDeliveryInfo(ctx, session, delivery.DeliveryInfo.Code, cartDomain.CreateDeliveryInfoUpdateCommand(delivery.DeliveryInfo)) 203 if err != nil { 204 c.logger.WithContext(ctx).Error(fmt.Errorf("error during delivery info update: %w", err)) 205 continue 206 } 207 208 for _, item := range delivery.Cartitems { 209 c.logger.WithContext(ctx).Debugf("adding guest cart item to customer cart: %v", item) 210 addRequest := cartDomain.AddRequest{ 211 MarketplaceCode: item.MarketplaceCode, 212 Qty: item.Qty, 213 VariantMarketplaceCode: item.VariantMarketPlaceCode, 214 AdditionalData: item.AdditionalData, 215 BundleConfiguration: item.BundleConfig, 216 } 217 218 _, err = c.cartService.AddProduct(ctx, session, delivery.DeliveryInfo.Code, addRequest) 219 if err != nil { 220 c.logger.WithContext(ctx).Error(fmt.Errorf("add to cart for guest item %v failed: %w", item, err)) 221 } 222 } 223 } 224 225 if guestCart.BillingAddress != nil { 226 err = c.cartService.UpdateBillingAddress(ctx, session, guestCart.BillingAddress) 227 if err != nil { 228 c.logger.WithContext(ctx).Error(fmt.Errorf("couldn't update billing address: %w", err)) 229 } 230 } 231 232 if guestCart.Purchaser != nil { 233 err = c.cartService.UpdatePurchaser(ctx, session, guestCart.Purchaser, &guestCart.AdditionalData) 234 if err != nil { 235 c.logger.WithContext(ctx).Error(fmt.Errorf("couldn't update purchaser: %w", err)) 236 } 237 } 238 239 if guestCart.HasAppliedCouponCode() { 240 for _, code := range guestCart.AppliedCouponCodes { 241 _, err = c.cartService.ApplyVoucher(ctx, session, code.Code) 242 if err != nil { 243 c.logger.WithContext(ctx).Error(fmt.Errorf("couldn't apply voucher %q: %w", code.Code, err)) 244 } 245 } 246 } 247 248 if guestCart.HasAppliedGiftCards() { 249 for _, code := range guestCart.AppliedGiftCards { 250 _, err = c.cartService.ApplyGiftCard(ctx, session, code.Code) 251 if err != nil { 252 c.logger.WithContext(ctx).Error(fmt.Errorf("couldn't apply gift card %q: %w", code.Code, err)) 253 } 254 } 255 } 256 257 if guestCart.PaymentSelection != nil { 258 err = c.cartService.UpdatePaymentSelection(ctx, session, guestCart.PaymentSelection) 259 if err != nil { 260 c.logger.WithContext(ctx).Error(fmt.Errorf("couldn't payment selection: %w", err)) 261 } 262 } 263 } 264 265 // Inject dependencies 266 func (c *CartMergeStrategyMerge) Inject( 267 logger flamingo.Logger, 268 cartService Service, 269 ) *CartMergeStrategyMerge { 270 c.logger = logger 271 c.cartService = cartService 272 273 return c 274 } 275 276 //nolint:cyclop,gocognit // setting all cart attributes is a complex task 277 func (c *CartMergeStrategyMerge) Merge(ctx context.Context, session *web.Session, guestCart cartDomain.Cart, customerCart cartDomain.Cart) { 278 var err error 279 280 for _, delivery := range guestCart.Deliveries { 281 c.logger.WithContext(ctx).Info(fmt.Sprintf("Merging delivery with code %v of guestCart with ID %v into customerCart with ID %v", delivery.DeliveryInfo.Code, guestCart.ID, customerCart.ID)) 282 283 err = c.cartService.UpdateDeliveryInfo(ctx, session, delivery.DeliveryInfo.Code, cartDomain.CreateDeliveryInfoUpdateCommand(delivery.DeliveryInfo)) 284 if err != nil { 285 c.logger.WithContext(ctx).Error("WebLoginEvent customerCart UpdateDeliveryInfo error", err) 286 continue 287 } 288 289 for _, item := range delivery.Cartitems { 290 c.logger.WithContext(ctx).Debugf("Merging item from guest to user cart %v", item) 291 addRequest := cartDomain.AddRequest{ 292 MarketplaceCode: item.MarketplaceCode, 293 Qty: item.Qty, 294 VariantMarketplaceCode: item.VariantMarketPlaceCode, 295 AdditionalData: item.AdditionalData, 296 BundleConfiguration: item.BundleConfig, 297 } 298 299 _, err = c.cartService.AddProduct(ctx, session, delivery.DeliveryInfo.Code, addRequest) 300 if err != nil { 301 c.logger.WithContext(ctx).Error("WebLoginEvent - customerCart product has merge error", addRequest.MarketplaceCode, err) 302 } 303 } 304 } 305 306 if customerCart.BillingAddress == nil && guestCart.BillingAddress != nil { 307 err = c.cartService.UpdateBillingAddress(ctx, session, guestCart.BillingAddress) 308 if err != nil { 309 c.logger.WithContext(ctx).Error("WebLoginEvent - customerCart UpdateBillingAddress error", err) 310 } 311 } 312 313 if customerCart.Purchaser == nil && guestCart.Purchaser != nil { 314 err = c.cartService.UpdatePurchaser(ctx, session, guestCart.Purchaser, &guestCart.AdditionalData) 315 if err != nil { 316 c.logger.WithContext(ctx).Error("WebLoginEvent - customerCart UpdatePurchaser error", err) 317 } 318 } 319 320 if guestCart.HasAppliedCouponCode() { 321 for _, code := range guestCart.AppliedCouponCodes { 322 _, err = c.cartService.ApplyVoucher(ctx, session, code.Code) 323 if err != nil { 324 c.logger.WithContext(ctx).Error("WebLoginEvent - customerCart ApplyVoucher has error", code.Code, err) 325 } 326 } 327 } 328 329 if guestCart.HasAppliedGiftCards() { 330 for _, code := range guestCart.AppliedGiftCards { 331 _, err = c.cartService.ApplyGiftCard(ctx, session, code.Code) 332 if err != nil { 333 c.logger.WithContext(ctx).Error("WebLoginEvent - customerCart ApplyGiftCard has error", code.Code, err) 334 } 335 } 336 } 337 338 if customerCart.PaymentSelection == nil && guestCart.PaymentSelection != nil && customerCart.ItemCount() == 0 { 339 err = c.cartService.UpdatePaymentSelection(ctx, session, guestCart.PaymentSelection) 340 if err != nil { 341 c.logger.WithContext(ctx).Error("WebLoginEvent - customerCart UpdatePaymentSelection error", err) 342 } 343 } 344 }