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 }