flamingo.me/flamingo-commerce/v3@v3.11.0/cart/application/cartReceiver.go (about)

     1  package application
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"go.opencensus.io/trace"
     9  
    10  	"flamingo.me/flamingo/v3/core/auth"
    11  	"flamingo.me/flamingo/v3/framework/web"
    12  
    13  	"flamingo.me/flamingo/v3/framework/flamingo"
    14  
    15  	cartDomain "flamingo.me/flamingo-commerce/v3/cart/domain/cart"
    16  	"flamingo.me/flamingo-commerce/v3/cart/domain/decorator"
    17  	"flamingo.me/flamingo-commerce/v3/customer/application"
    18  )
    19  
    20  type (
    21  	// CartReceiverService provides methods to get the correct cart
    22  	CartReceiverService struct {
    23  		cartDecoratorFactory *decorator.DecoratedCartFactory
    24  		*BaseCartReceiver
    25  	}
    26  
    27  	// BaseCartReceiver get undecorated carts only
    28  	BaseCartReceiver struct {
    29  		guestCartService    cartDomain.GuestCartService
    30  		customerCartService cartDomain.CustomerCartService
    31  		webIdentityService  *auth.WebIdentityService
    32  		eventRouter         flamingo.EventRouter
    33  		logger              flamingo.Logger
    34  		// CartCache is optional
    35  		cartCache CartCache
    36  	}
    37  )
    38  
    39  var (
    40  	// ErrTemporaryCartService should be returned if it is likely that the backend service will return a cart on a next try
    41  	ErrTemporaryCartService = errors.New("the cart could not be received currently - try again later")
    42  )
    43  
    44  const (
    45  	// GuestCartSessionKey is a prefix
    46  	GuestCartSessionKey = "cart.guestid"
    47  )
    48  
    49  // Inject the dependencies
    50  func (cs *CartReceiverService) Inject(
    51  	guestCartService cartDomain.GuestCartService,
    52  	customerCartService cartDomain.CustomerCartService,
    53  	cartDecoratorFactory *decorator.DecoratedCartFactory,
    54  	webIdentityService *auth.WebIdentityService,
    55  	logger flamingo.Logger,
    56  	eventRouter flamingo.EventRouter,
    57  	optionals *struct {
    58  		CartCache CartCache `inject:",optional"`
    59  	},
    60  ) {
    61  	cs.cartDecoratorFactory = cartDecoratorFactory
    62  	cs.BaseCartReceiver = &BaseCartReceiver{}
    63  	cs.BaseCartReceiver.Inject(
    64  		guestCartService,
    65  		customerCartService,
    66  		webIdentityService,
    67  		logger,
    68  		eventRouter,
    69  		optionals)
    70  }
    71  
    72  // Inject the dependencies
    73  func (cs *BaseCartReceiver) Inject(
    74  	guestCartService cartDomain.GuestCartService,
    75  	customerCartService cartDomain.CustomerCartService,
    76  	webIdentityService *auth.WebIdentityService,
    77  	logger flamingo.Logger,
    78  	eventRouter flamingo.EventRouter,
    79  	optionals *struct {
    80  		CartCache CartCache `inject:",optional"`
    81  	},
    82  ) {
    83  	cs.guestCartService = guestCartService
    84  	cs.customerCartService = customerCartService
    85  	cs.webIdentityService = webIdentityService
    86  	cs.logger = logger.WithField("module", "cart").WithField(flamingo.LogKeyCategory, "checkout.cartreceiver")
    87  	cs.eventRouter = eventRouter
    88  
    89  	if optionals != nil {
    90  		cs.cartCache = optionals.CartCache
    91  	}
    92  }
    93  
    94  // RestoreCart restores a previously used guest / customer cart
    95  // deprecated: use CartService.RestoreCart(), ensure that your cart implements the CompleteBehaviour
    96  func (cs *BaseCartReceiver) RestoreCart(ctx context.Context, session *web.Session, cartToRestore cartDomain.Cart) (*cartDomain.Cart, error) {
    97  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/RestoreCart")
    98  	defer span.End()
    99  
   100  	identity := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
   101  	if identity != nil {
   102  		restoredCart, err := cs.customerCartService.RestoreCart(ctx, identity, cartToRestore)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  
   107  		_ = cs.storeCartInCacheIfCacheIsEnabled(ctx, session, restoredCart)
   108  		return restoredCart, nil
   109  	}
   110  
   111  	restoredCart, err := cs.guestCartService.RestoreCart(ctx, cartToRestore)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	session.Store(GuestCartSessionKey, restoredCart.ID)
   117  	_ = cs.storeCartInCacheIfCacheIsEnabled(ctx, session, restoredCart)
   118  	return restoredCart, nil
   119  }
   120  
   121  // ShouldHaveCart - checks if there should be a cart. Indicated if a call to GetCart should return a real cart
   122  func (cs *BaseCartReceiver) ShouldHaveCart(ctx context.Context, session *web.Session) bool {
   123  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/ShouldHaveCart")
   124  	defer span.End()
   125  
   126  	if cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx)) != nil {
   127  		return true
   128  	}
   129  
   130  	return cs.ShouldHaveGuestCart(session)
   131  }
   132  
   133  // ShouldHaveGuestCart - checks if there should be guest cart
   134  func (cs *BaseCartReceiver) ShouldHaveGuestCart(session *web.Session) bool {
   135  	_, ok := session.Load(GuestCartSessionKey)
   136  	return ok
   137  }
   138  
   139  // ViewDecoratedCart  return a Cart for view
   140  func (cs *CartReceiverService) ViewDecoratedCart(ctx context.Context, session *web.Session) (*decorator.DecoratedCart, error) {
   141  	ctx, span := trace.StartSpan(ctx, "cart/CartReceiverService/ViewDecoratedCart")
   142  	defer span.End()
   143  
   144  	cart, err := cs.ViewCart(ctx, session)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return cs.DecorateCart(ctx, cart)
   150  }
   151  
   152  // ViewDecoratedCartWithoutCache  return a Cart for view
   153  func (cs *CartReceiverService) ViewDecoratedCartWithoutCache(ctx context.Context, session *web.Session) (*decorator.DecoratedCart, error) {
   154  	ctx, span := trace.StartSpan(ctx, "cart/CartReceiverService/ViewDecoratedCartWithoutCache")
   155  	defer span.End()
   156  
   157  	cart, err := cs.GetCartWithoutCache(ctx, session)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	return cs.DecorateCart(ctx, cart)
   163  }
   164  
   165  // ViewCart  return a Cart for view
   166  func (cs *BaseCartReceiver) ViewCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, error) {
   167  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/ViewCart")
   168  	defer span.End()
   169  
   170  	if cs.ShouldHaveCart(ctx, session) {
   171  		cart, _, err := cs.GetCart(ctx, session)
   172  		if err != nil {
   173  			return cs.getEmptyCart(), err
   174  		}
   175  
   176  		return cart, nil
   177  	}
   178  
   179  	return cs.getEmptyCart(), nil
   180  }
   181  
   182  func (cs *BaseCartReceiver) storeCartInCacheIfCacheIsEnabled(ctx context.Context, session *web.Session, cart *cartDomain.Cart) error {
   183  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/storeCartInCacheIfCacheIsEnabled")
   184  	defer span.End()
   185  
   186  	if cs.cartCache == nil {
   187  		return errors.New("no cache")
   188  	}
   189  
   190  	id, err := cs.cartCache.BuildIdentifier(ctx, session)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	return cs.cartCache.CacheCart(ctx, session, id, cart)
   196  }
   197  
   198  // GetCart Get the correct Cart (either Guest or User)
   199  func (cs *BaseCartReceiver) GetCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, cartDomain.ModifyBehaviour, error) {
   200  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/GetCart")
   201  	defer span.End()
   202  
   203  	if cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx)) != nil {
   204  		return cs.getCustomerCart(ctx, session)
   205  	}
   206  	if cs.ShouldHaveGuestCart(session) {
   207  		return cs.getExistingGuestCart(ctx, session)
   208  	}
   209  	return cs.getNewGuestCart(ctx, session)
   210  }
   211  
   212  // ModifyBehaviour returns the correct behaviour to modify the cart for the current user (guest/customer)
   213  func (cs *BaseCartReceiver) ModifyBehaviour(ctx context.Context) (cartDomain.ModifyBehaviour, error) {
   214  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/ModifyBehaviour")
   215  	defer span.End()
   216  
   217  	identity := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
   218  	if identity != nil {
   219  		return cs.customerCartService.GetModifyBehaviour(ctx, identity)
   220  	}
   221  	return cs.guestCartService.GetModifyBehaviour(ctx)
   222  }
   223  
   224  // getCustomerCart
   225  func (cs *BaseCartReceiver) getCustomerCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, cartDomain.ModifyBehaviour, error) {
   226  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/getCustomerCart")
   227  	defer span.End()
   228  
   229  	cart, found, err := cs.getCartFromCacheIfCacheIsEnabled(ctx, session)
   230  
   231  	if err != nil {
   232  		if errors.Is(err, ErrCacheIsInvalid) || errors.Is(err, ErrNoCacheEntry) {
   233  			cs.logger.WithContext(ctx).Info(err)
   234  		} else {
   235  			cs.logger.WithContext(ctx).Error(err)
   236  		}
   237  	}
   238  
   239  	identitiy := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
   240  	if identitiy == nil {
   241  		return nil, nil, application.ErrNoIdentity
   242  	}
   243  
   244  	if !found {
   245  		cart, err = cs.customerCartService.GetCart(ctx, identitiy, "me")
   246  		if err != nil {
   247  			return nil, nil, err
   248  		}
   249  		_ = cs.storeCartInCacheIfCacheIsEnabled(ctx, session, cart)
   250  	}
   251  
   252  	behaviour, err := cs.customerCartService.GetModifyBehaviour(ctx, identitiy)
   253  	if err != nil {
   254  		return nil, nil, err
   255  	}
   256  
   257  	return cart, behaviour, nil
   258  }
   259  
   260  func (cs *BaseCartReceiver) getCartFromCacheIfCacheIsEnabled(ctx context.Context, session *web.Session) (*cartDomain.Cart, bool, error) {
   261  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/getCartFromCacheIfCacheIsEnabled")
   262  	defer span.End()
   263  
   264  	if cs.cartCache == nil {
   265  		return nil, false, nil
   266  	}
   267  	cacheID, err := cs.cartCache.BuildIdentifier(ctx, session)
   268  
   269  	if err != nil {
   270  		return nil, false, err
   271  	}
   272  	cs.logger.WithContext(ctx).Debug("query cart cache %#v", cacheID)
   273  
   274  	cart, cacheErr := cs.cartCache.GetCart(ctx, session, cacheID)
   275  	if errors.Is(cacheErr, ErrNoCacheEntry) {
   276  		return nil, false, nil
   277  	}
   278  
   279  	if cacheErr != nil {
   280  		return nil, false, cacheErr
   281  	}
   282  
   283  	return cart, true, nil
   284  }
   285  
   286  // getExistingGuestCart
   287  func (cs *BaseCartReceiver) getExistingGuestCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, cartDomain.ModifyBehaviour, error) {
   288  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/getExistingGuestCart")
   289  	defer span.End()
   290  
   291  	cart, found, err := cs.getCartFromCacheIfCacheIsEnabled(ctx, session)
   292  
   293  	if err != nil {
   294  		if errors.Is(err, ErrCacheIsInvalid) || errors.Is(err, ErrNoCacheEntry) {
   295  			cs.logger.WithContext(ctx).Info(err)
   296  		} else {
   297  			cs.logger.WithContext(ctx).Error(err)
   298  		}
   299  	}
   300  
   301  	if err != nil || !found {
   302  		cart, err = cs.getSessionGuestCart(ctx, session)
   303  
   304  		if err != nil {
   305  			// TODO - decide on recoverable errors (where we should communicate "try again" / and not recoverable (where we should clean up guest cart in session and try to get a new one)
   306  			cs.logger.WithContext(ctx).Warn("GetCart - No cart in session return empty")
   307  
   308  			// delete(ctx.Session().Values, "cart.guestid")
   309  			return nil, nil, ErrTemporaryCartService
   310  		}
   311  
   312  		_ = cs.storeCartInCacheIfCacheIsEnabled(ctx, session, cart)
   313  		cs.logger.WithContext(ctx).Debug("guestcart not in cache - requested and passed to cache")
   314  	}
   315  
   316  	behaviour, err := cs.guestCartService.GetModifyBehaviour(ctx)
   317  	if err != nil {
   318  		return nil, nil, err
   319  	}
   320  
   321  	if cart == nil {
   322  		cs.logger.WithContext(ctx).Error("Something unexpected went wrong! No guestcart!")
   323  
   324  		return nil, nil, errors.New("something unexpected went wrong - no guestcart")
   325  	}
   326  
   327  	return cart, behaviour, nil
   328  }
   329  
   330  // getNewGuestCart
   331  func (cs *BaseCartReceiver) getNewGuestCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, cartDomain.ModifyBehaviour, error) {
   332  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/getNewGuestCart")
   333  	defer span.End()
   334  
   335  	guestCart, err := cs.guestCartService.GetNewCart(ctx)
   336  	if err != nil {
   337  		if !errors.Is(err, context.Canceled) {
   338  			cs.logger.WithContext(ctx).Error("Cannot create a new guest cart. Error: ", err)
   339  		}
   340  
   341  		return nil, nil, err
   342  	}
   343  
   344  	cs.logger.WithContext(ctx).Info("Requested new guest cart: ", guestCart)
   345  	session.Store(GuestCartSessionKey, guestCart.ID)
   346  	_ = cs.storeCartInCacheIfCacheIsEnabled(ctx, session, guestCart)
   347  	behaviour, err := cs.guestCartService.GetModifyBehaviour(ctx)
   348  
   349  	if err != nil {
   350  		return guestCart, nil, err
   351  	}
   352  
   353  	if guestCart == nil {
   354  		cs.logger.WithContext(ctx).Error("Something unexpected went wrong! No guest cart!")
   355  
   356  		return nil, nil, errors.New("something unexpected went wrong - no guest cart")
   357  	}
   358  
   359  	return guestCart, behaviour, nil
   360  }
   361  
   362  // GetCartWithoutCache - forces to get the cart without cache
   363  func (cs *BaseCartReceiver) GetCartWithoutCache(ctx context.Context, session *web.Session) (*cartDomain.Cart, error) {
   364  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/GetCartWithoutCache")
   365  	defer span.End()
   366  
   367  	// Invalidate cart cache
   368  	if cs.eventRouter != nil {
   369  		cs.eventRouter.Dispatch(ctx, &cartDomain.InvalidateCartEvent{Session: session})
   370  	}
   371  
   372  	identitiy := cs.webIdentityService.Identify(ctx, web.RequestFromContext(ctx))
   373  	if identitiy != nil {
   374  		return cs.customerCartService.GetCart(ctx, identitiy, "me")
   375  	}
   376  
   377  	if cs.ShouldHaveGuestCart(session) {
   378  		return cs.getSessionGuestCart(ctx, session)
   379  	}
   380  
   381  	return cs.guestCartService.GetNewCart(ctx)
   382  
   383  }
   384  
   385  // ViewGuestCart try to get the guest Cart - even if the user is logged in
   386  func (cs *BaseCartReceiver) ViewGuestCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, error) {
   387  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/ViewGuestCart")
   388  	defer span.End()
   389  
   390  	if cs.ShouldHaveGuestCart(session) {
   391  		guestCart, err := cs.getSessionGuestCart(ctx, session)
   392  		if err != nil {
   393  			// TODO - decide on recoverable errors (where we should communicate "try again" / and not recoverable (where we should clean up guest cart in session and try to get a new one)
   394  			cs.logger.WithContext(ctx).Warn("GetCart - No cart in session return empty")
   395  
   396  			return nil, ErrTemporaryCartService
   397  		}
   398  
   399  		return guestCart, nil
   400  	}
   401  
   402  	return cs.getEmptyCart(), nil
   403  }
   404  
   405  // DeleteSavedSessionGuestCartID deletes a guest cart Key from the Session Values
   406  func (cs *CartService) DeleteSavedSessionGuestCartID(session *web.Session) error {
   407  	session.Delete(GuestCartSessionKey)
   408  
   409  	// TODO - trigger backend also to be able to delete the cart there ( cs.GuestCartService.DeleteCart())
   410  	return nil
   411  }
   412  
   413  // getSessionGuestCart
   414  func (cs *BaseCartReceiver) getSessionGuestCart(ctx context.Context, session *web.Session) (*cartDomain.Cart, error) {
   415  	ctx, span := trace.StartSpan(ctx, "cart/BaseCartReceiver/getSessionGuestCart")
   416  	defer span.End()
   417  
   418  	if guestcartid, ok := session.Load(GuestCartSessionKey); ok {
   419  		existingCart, err := cs.guestCartService.GetCart(ctx, guestcartid.(string))
   420  		if err != nil {
   421  			cs.logger.WithContext(ctx).Warn(fmt.Sprintf("Guest cart with ID %q cannot be retrieved. Error: %s", guestcartid, err))
   422  			// we seem to have an erratic session cart - remove it
   423  			session.Delete(GuestCartSessionKey)
   424  		}
   425  
   426  		return existingCart, err
   427  	}
   428  
   429  	cs.logger.WithContext(ctx).Error("No cart in session yet - getSessionGuestCart should be called only if HasSessionGuestCart returns true")
   430  
   431  	return nil, errors.New("no cart in session yet")
   432  }
   433  
   434  // DecorateCart Get the correct Cart
   435  func (cs *CartReceiverService) DecorateCart(ctx context.Context, cart *cartDomain.Cart) (*decorator.DecoratedCart, error) {
   436  	ctx, span := trace.StartSpan(ctx, "cart/CartReceiverService/DecorateCart")
   437  	defer span.End()
   438  
   439  	if cart == nil {
   440  		return nil, errors.New("no cart given")
   441  	}
   442  
   443  	return cs.cartDecoratorFactory.Create(ctx, *cart), nil
   444  }
   445  
   446  // GetDecoratedCart Get the correct Cart
   447  func (cs *CartReceiverService) GetDecoratedCart(ctx context.Context, session *web.Session) (*decorator.DecoratedCart, cartDomain.ModifyBehaviour, error) {
   448  	ctx, span := trace.StartSpan(ctx, "cart/CartReceiverService/GetDecoratedCart")
   449  	defer span.End()
   450  
   451  	cart, behaviour, err := cs.GetCart(ctx, session)
   452  	if err != nil {
   453  		return nil, nil, err
   454  	}
   455  
   456  	return cs.cartDecoratorFactory.Create(ctx, *cart), behaviour, nil
   457  }
   458  
   459  func (cs *BaseCartReceiver) getEmptyCart() *cartDomain.Cart {
   460  	return &cartDomain.Cart{}
   461  }