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  }