flamingo.me/flamingo-commerce/v3@v3.11.0/cart/interfaces/controller/cartapicontroller.go (about) 1 package controller 2 3 import ( 4 "context" 5 "net/url" 6 "strconv" 7 8 "go.opencensus.io/trace" 9 10 "flamingo.me/flamingo-commerce/v3/cart/domain/validation" 11 12 formDomain "flamingo.me/form/domain" 13 14 "flamingo.me/flamingo-commerce/v3/cart/interfaces/controller/forms" 15 16 "flamingo.me/flamingo/v3/framework/flamingo" 17 "flamingo.me/flamingo/v3/framework/web" 18 19 "flamingo.me/flamingo-commerce/v3/cart/application" 20 "flamingo.me/flamingo-commerce/v3/cart/domain/cart" 21 ) 22 23 type ( 24 // CartAPIController for cart api 25 CartAPIController struct { 26 responder *web.Responder 27 cartService *application.CartService 28 cartReceiverService *application.CartReceiverService 29 logger flamingo.Logger 30 billingAddressFormController *forms.BillingAddressFormController 31 deliveryFormController *forms.DeliveryFormController 32 simplePaymentFormController *forms.SimplePaymentFormController 33 } 34 35 // CartAPIResult view data 36 CartAPIResult struct { 37 // Contains details if success is false 38 Error *resultError 39 Success bool 40 CartTeaser *cart.Teaser 41 Data interface{} 42 DataValidationInfo *formDomain.ValidationInfo `swaggertype:"object"` 43 CartValidationResult *validation.Result 44 } 45 46 getCartResult struct { 47 Cart *cart.Cart 48 CartValidationResult *validation.Result 49 } 50 51 resultError struct { 52 Message string 53 Code string 54 } // @name cartResultError 55 56 messageCodeAvailable interface { 57 MessageCode() string 58 } 59 60 // PromotionFunction type takes ctx, cart, couponCode and applies the promotion 61 promotionFunc func(ctx context.Context, session *web.Session, couponCode string) (*cart.Cart, error) 62 ) 63 64 // Inject dependencies 65 func (cc *CartAPIController) Inject( 66 responder *web.Responder, 67 ApplicationCartService *application.CartService, 68 ApplicationCartReceiverService *application.CartReceiverService, 69 billingAddressFormController *forms.BillingAddressFormController, 70 deliveryFormController *forms.DeliveryFormController, 71 simplePaymentFormController *forms.SimplePaymentFormController, 72 Logger flamingo.Logger, 73 ) { 74 cc.responder = responder 75 cc.cartService = ApplicationCartService 76 cc.cartReceiverService = ApplicationCartReceiverService 77 cc.logger = Logger.WithField("category", "CartApiController") 78 cc.billingAddressFormController = billingAddressFormController 79 cc.deliveryFormController = deliveryFormController 80 cc.simplePaymentFormController = simplePaymentFormController 81 } 82 83 // GetAction Get JSON Format of API 84 // @Summary Get the current cart 85 // @Tags Cart 86 // @Produce json 87 // @Success 200 {object} getCartResult 88 // @Failure 500 {object} CartAPIResult 89 // @Router /api/v1/cart [get] 90 func (cc *CartAPIController) GetAction(ctx context.Context, r *web.Request) web.Result { 91 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/GetAction") 92 defer span.End() 93 94 decoratedCart, e := cc.cartReceiverService.ViewDecoratedCart(ctx, r.Session()) 95 if e != nil { 96 result := newResult() 97 result.SetError(e, "get_error") 98 cc.logger.WithContext(ctx).Error("cart.cartapicontroller.get: %v", e.Error()) 99 return cc.responder.Data(result).Status(500) 100 } 101 validationResult := cc.cartService.ValidateCart(ctx, web.SessionFromContext(ctx), decoratedCart) 102 return cc.responder.Data(getCartResult{ 103 CartValidationResult: &validationResult, 104 Cart: &decoratedCart.Cart, 105 }) 106 } 107 108 // DeleteCartAction removes all cart content and returns a blank cart 109 // @Summary Remove all stored cart information e.g. items, deliveries, billing address and returns the empty cart. 110 // @Tags Cart 111 // @Produce json 112 // @Success 200 {object} CartAPIResult 113 // @Failure 500 {object} CartAPIResult 114 // @Router /api/v1/cart [delete] 115 func (cc *CartAPIController) DeleteCartAction(ctx context.Context, r *web.Request) web.Result { 116 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/DeleteCartAction") 117 defer span.End() 118 119 err := cc.cartService.Clean(ctx, r.Session()) 120 121 result := newResult() 122 if err != nil { 123 cc.logger.WithContext(ctx).Error("cart.cartapicontroller.delete: %v", err.Error()) 124 125 result.SetError(err, "delete_cart_error") 126 response := cc.responder.Data(result) 127 response.Status(500) 128 return response 129 } 130 cc.enrichResultWithCartInfos(ctx, &result) 131 return cc.responder.Data(result) 132 } 133 134 // AddAction Add Item to cart 135 // @Summary Add Item to cart 136 // @Tags Cart 137 // @Produce json 138 // @Success 200 {object} CartAPIResult 139 // @Failure 500 {object} CartAPIResult 140 // @Param deliveryCode path string true "the identifier for the delivery in the cart" 141 // @Param marketplaceCode query string true "the product identifier that should be added" 142 // @Param variantMarketplaceCode query string false "optional the product identifier of the variant (for configurable products) that should be added" 143 // @Param qty query integer false "optional the qty that should be added" 144 // @Router /api/v1/cart/delivery/{deliveryCode}/item [post] 145 func (cc *CartAPIController) AddAction(ctx context.Context, r *web.Request) web.Result { 146 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/AddAction") 147 defer span.End() 148 149 variantMarketplaceCode := r.Params["variantMarketplaceCode"] 150 151 qty, ok := r.Params["qty"] 152 if !ok { 153 qty = "1" 154 } 155 qtyInt, _ := strconv.Atoi(qty) 156 deliveryCode := r.Params["deliveryCode"] 157 158 addRequest := cc.cartService.BuildAddRequest(ctx, r.Params["marketplaceCode"], variantMarketplaceCode, qtyInt, nil) 159 _, err := cc.cartService.AddProduct(ctx, r.Session(), deliveryCode, addRequest) 160 161 result := newResult() 162 if err != nil { 163 cc.logger.WithContext(ctx).Error("cart.cartapicontroller.add: %v", err.Error()) 164 165 result.SetError(err, "add_product_error") 166 response := cc.responder.Data(result) 167 response.Status(500) 168 return response 169 } 170 cc.enrichResultWithCartInfos(ctx, &result) 171 return cc.responder.Data(result) 172 } 173 174 // DeleteItemAction deletes an item from the cart 175 // @Summary Delete item from cart 176 // @Tags Cart 177 // @Produce json 178 // @Success 200 {object} CartAPIResult 179 // @Failure 500 {object} CartAPIResult 180 // @Param deliveryCode path string true "the identifier for the delivery in the cart" 181 // @Param itemID query string true "the item that should be deleted" 182 // @Router /api/v1/cart/delivery/{deliveryCode}/item [delete] 183 func (cc *CartAPIController) DeleteItemAction(ctx context.Context, r *web.Request) web.Result { 184 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/DeleteItemAction") 185 defer span.End() 186 187 itemID, _ := r.Query1("itemID") 188 deliveryCode := r.Params["deliveryCode"] 189 190 err := cc.cartService.DeleteItem(ctx, r.Session(), itemID, deliveryCode) 191 192 result := newResult() 193 if err != nil { 194 cc.logger.WithContext(ctx).Error("cart.cartapicontroller.delete: %v", err.Error()) 195 196 result.SetError(err, "delete_item_error") 197 response := cc.responder.Data(result) 198 response.Status(500) 199 return response 200 } 201 cc.enrichResultWithCartInfos(ctx, &result) 202 return cc.responder.Data(result) 203 } 204 205 // UpdateItemAction updates the item qty in the current cart 206 // @Summary Update item in the cart 207 // @Tags Cart 208 // @Produce json 209 // @Success 200 {object} CartAPIResult 210 // @Failure 500 {object} CartAPIResult 211 // @Param deliveryCode path string true "the identifier for the delivery in the cart" 212 // @Param itemID query string true "the item that should be updated" 213 // @Param qty query integer true "the new qty" 214 // @Router /api/v1/cart/delivery/{deliveryCode}/item [put] 215 func (cc *CartAPIController) UpdateItemAction(ctx context.Context, r *web.Request) web.Result { 216 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/UpdateItemAction") 217 defer span.End() 218 219 itemID, _ := r.Query1("itemID") 220 deliveryCode := r.Params["deliveryCode"] 221 qty, ok := r.Params["qty"] 222 if !ok { 223 qty = "1" 224 } 225 qtyInt, _ := strconv.Atoi(qty) 226 227 err := cc.cartService.UpdateItemQty(ctx, r.Session(), itemID, deliveryCode, qtyInt) 228 229 result := newResult() 230 if err != nil { 231 cc.logger.WithContext(ctx).Error("cart.cartapicontroller.updateItem: %v", err.Error()) 232 233 result.SetError(err, "update_item_error") 234 response := cc.responder.Data(result) 235 response.Status(500) 236 return response 237 } 238 cc.enrichResultWithCartInfos(ctx, &result) 239 return cc.responder.Data(result) 240 } 241 242 // ApplyVoucherAndGetAction applies the given voucher and returns the cart 243 // @Summary Apply Voucher Code 244 // @Tags Cart 245 // @Produce json 246 // @Success 200 {object} CartAPIResult 247 // @Failure 500 {object} CartAPIResult 248 // @Param couponCode query string true "the couponCode that should be applied" 249 // @Router /api/v1/cart/voucher [post] 250 func (cc *CartAPIController) ApplyVoucherAndGetAction(ctx context.Context, r *web.Request) web.Result { 251 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/ApplyVoucherAndGetAction") 252 defer span.End() 253 254 return cc.handlePromotionAction(ctx, r, "voucher_error", cc.cartService.ApplyVoucher) 255 } 256 257 // RemoveVoucherAndGetAction removes the given voucher and returns the cart 258 // @Summary Remove Voucher Code 259 // @Tags Cart 260 // @Produce json 261 // @Success 200 {object} CartAPIResult 262 // @Failure 500 {object} CartAPIResult 263 // @Param couponCode query string true "the couponCode that should be applied" 264 // @Router /api/v1/cart/voucher [delete] 265 func (cc *CartAPIController) RemoveVoucherAndGetAction(ctx context.Context, r *web.Request) web.Result { 266 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/RemoveVoucherAndGetAction") 267 defer span.End() 268 269 return cc.handlePromotionAction(ctx, r, "voucher_error", cc.cartService.RemoveVoucher) 270 } 271 272 // DeleteAllItemsAction removes all cart items and returns the cart 273 // @Summary Remove all cart items from all deliveries and return the cart, keeps the delivery info untouched. 274 // @Tags Cart 275 // @Produce json 276 // @Success 200 {object} CartAPIResult 277 // @Failure 500 {object} CartAPIResult 278 // @Router /api/v1/cart/deliveries/items [delete] 279 func (cc *CartAPIController) DeleteAllItemsAction(ctx context.Context, r *web.Request) web.Result { 280 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/DeleteAllItemsAction") 281 defer span.End() 282 283 err := cc.cartService.DeleteAllItems(ctx, r.Session()) 284 result := newResult() 285 if err != nil { 286 result.SetError(err, "delete_items_error") 287 response := cc.responder.Data(result) 288 response.Status(500) 289 return response 290 } 291 return cc.responder.Data(result) 292 } 293 294 // ApplyGiftCardAndGetAction applies the given gift card and returns the cart 295 // the request needs a query string param "couponCode" which includes the corresponding gift card code 296 // @Summary Apply Gift Card 297 // @Tags Cart 298 // @Produce json 299 // @Success 200 {object} CartAPIResult 300 // @Failure 500 {object} CartAPIResult 301 // @Param couponCode query string true "the gift card code" 302 // @Router /api/v1/cart/gift-card [post] 303 func (cc *CartAPIController) ApplyGiftCardAndGetAction(ctx context.Context, r *web.Request) web.Result { 304 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/ApplyGiftCardAndGetAction") 305 defer span.End() 306 307 return cc.handlePromotionAction(ctx, r, "giftcard_error", cc.cartService.ApplyGiftCard) 308 } 309 310 // ApplyCombinedVoucherGift applies a given code (which might be either a voucher or a Gift Card code) to the 311 // cartService and returns the cart 312 // @Summary Apply Gift Card or Voucher (auto detected) 313 // @Description Use this if you have one user input and that input can be used to either enter a voucher or a gift card 314 // @Tags Cart 315 // @Produce json 316 // @Success 200 {object} CartAPIResult 317 // @Failure 500 {object} CartAPIResult 318 // @Param couponCode query string true "the couponCode that should be applied as gift card or voucher" 319 // @Router /api/v1/cart/voucher-gift-card [post] 320 func (cc *CartAPIController) ApplyCombinedVoucherGift(ctx context.Context, r *web.Request) web.Result { 321 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/ApplyCombinedVoucherGift") 322 defer span.End() 323 324 return cc.handlePromotionAction(ctx, r, "applyany_error", cc.cartService.ApplyAny) 325 } 326 327 // RemoveGiftCardAndGetAction removes the given gift card and returns the cart 328 // the request needs a query string param "couponCode" which includes the corresponding gift card code 329 // @Summary Remove Gift Card 330 // @Tags Cart 331 // @Produce json 332 // @Success 200 {object} CartAPIResult 333 // @Failure 500 {object} CartAPIResult 334 // @Param couponCode query string true "the couponCode that should be deleted as gift card" 335 // @Router /api/v1/cart/gift-card [delete] 336 func (cc *CartAPIController) RemoveGiftCardAndGetAction(ctx context.Context, r *web.Request) web.Result { 337 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/RemoveGiftCardAndGetAction") 338 defer span.End() 339 340 return cc.handlePromotionAction(ctx, r, "giftcard_error", cc.cartService.RemoveGiftCard) 341 } 342 343 // handles promotion action 344 func (cc *CartAPIController) handlePromotionAction(ctx context.Context, r *web.Request, errorCode string, fn promotionFunc) web.Result { 345 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/handlePromotionAction") 346 defer span.End() 347 348 couponCode := r.Params["couponCode"] 349 result := newResult() 350 _, err := fn(ctx, r.Session(), couponCode) 351 if err != nil { 352 cc.enrichResultWithCartInfos(ctx, &result) 353 result.SetError(err, errorCode) 354 response := cc.responder.Data(result) 355 response.Status(500) 356 357 return response 358 } 359 cc.enrichResultWithCartInfos(ctx, &result) 360 return cc.responder.Data(result) 361 } 362 363 // DeleteDelivery cleans the given delivery from the cart and returns the cleaned cart 364 // @Summary Cleans the given delivery from the cart 365 // @Tags Cart 366 // @Produce json 367 // @Success 200 {object} CartAPIResult 368 // @Failure 500 {object} CartAPIResult 369 // @Param deliveryCode path string true "the identifier for the delivery in the cart" 370 // @Router /api/v1/cart/delivery/{deliveryCode} [delete] 371 func (cc *CartAPIController) DeleteDelivery(ctx context.Context, r *web.Request) web.Result { 372 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/DeleteDelivery") 373 defer span.End() 374 375 result := newResult() 376 deliveryCode := r.Params["deliveryCode"] 377 _, err := cc.cartService.DeleteDelivery(ctx, r.Session(), deliveryCode) 378 if err != nil { 379 result.SetError(err, "delete_delivery_error") 380 return cc.responder.Data(result).Status(500) 381 } 382 cc.enrichResultWithCartInfos(ctx, &result) 383 return cc.responder.Data(result) 384 } 385 386 // BillingAction adds billing infos 387 // @Summary Adds billing infos to the current cart 388 // @Tags Cart 389 // @Accept x-www-form-urlencoded 390 // @Produce json 391 // @Success 200 {object} CartAPIResult 392 // @Failure 500 {object} CartAPIResult 393 // @Param vat formData string false "vat" 394 // @Param firstname formData string true "firstname" 395 // @Param lastname formData string true "lastname" 396 // @Param middlename formData string false "middlename" 397 // @Param title formData string false "title" 398 // @Param salutation formData string false "salutation" 399 // @Param street formData string false "street" 400 // @Param streetNr formData string false "streetNr" 401 // @Param addressLine1 formData string false "addressLine1" 402 // @Param addressLine2 formData string false "addressLine2" 403 // @Param company formData string false "company" 404 // @Param postCode formData string false "postCode" 405 // @Param city formData string false "city" 406 // @Param state formData string false "state" 407 // @Param regionCode formData string false "regionCode" 408 // @Param country formData string false "country" 409 // @Param countryCode formData string false "countryCode" 410 // @Param phoneAreaCode formData string false "phoneAreaCode" 411 // @Param phoneCountryCode formData string false "phoneCountryCode" 412 // @Param phoneNumber formData string false "phoneNumber" 413 // @Param email formData string true "email" 414 // @Router /api/v1/cart/billing [put] 415 func (cc *CartAPIController) BillingAction(ctx context.Context, r *web.Request) web.Result { 416 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/BillingAction") 417 defer span.End() 418 419 result := newResult() 420 form, success, err := cc.billingAddressFormController.HandleFormAction(ctx, r) 421 result.Success = success 422 if err != nil { 423 result.SetError(err, "form_error") 424 return cc.responder.Data(result) 425 } 426 427 if form != nil { 428 result.Data = form.Data 429 result.DataValidationInfo = &form.ValidationInfo 430 } 431 cc.enrichResultWithCartInfos(ctx, &result) 432 return cc.responder.Data(result) 433 } 434 435 // UpdateDeliveryInfoAction updates the delivery info 436 // @Summary Adds delivery infos, such as shipping address to the delivery for the cart 437 // @Tags Cart 438 // @Accept x-www-form-urlencoded 439 // @Produce json 440 // @Success 200 {object} CartAPIResult 441 // @Failure 500 {object} CartAPIResult 442 // @Param deliveryCode path string true "the identifier for the delivery in the cart" 443 // @Param deliveryAddress.vat formData string false "vat" 444 // @Param deliveryAddress.firstname formData string true "firstname" 445 // @Param deliveryAddress.lastname formData string true "lastname" 446 // @Param deliveryAddress.middlename formData string false "middlename" 447 // @Param deliveryAddress.title formData string false "title" 448 // @Param deliveryAddress.salutation formData string false "salutation" 449 // @Param deliveryAddress.street formData string false "street" 450 // @Param deliveryAddress.streetNr formData string false "streetNr" 451 // @Param deliveryAddress.addressLine1 formData string false "addressLine1" 452 // @Param deliveryAddress.addressLine2 formData string false "addressLine2" 453 // @Param deliveryAddress.company formData string false "company" 454 // @Param deliveryAddress.postCode formData string false "postCode" 455 // @Param deliveryAddress.city formData string false "city" 456 // @Param deliveryAddress.state formData string false "state" 457 // @Param deliveryAddress.regionCode formData string false "regionCode" 458 // @Param deliveryAddress.country formData string false "country" 459 // @Param deliveryAddress.countryCode formData string false "countryCode" 460 // @Param deliveryAddress.phoneAreaCode formData string false "phoneAreaCode" 461 // @Param deliveryAddress.phoneCountryCode formData string false "phoneCountryCode" 462 // @Param deliveryAddress.phoneNumber formData string false "phoneNumber" 463 // @Param deliveryAddress.email formData string true "email" 464 // @Param useBillingAddress formData bool false "useBillingAddress" 465 // @Param shippingMethod formData string false "shippingMethod" 466 // @Param shippingCarrier formData string false "shippingCarrier" 467 // @Param locationCode formData string false "locationCode" 468 // @Param desiredTime formData string false "desired date/time in RFC3339" format(date-time) 469 // @Router /api/v1/cart/delivery/{deliveryCode} [put] 470 func (cc *CartAPIController) UpdateDeliveryInfoAction(ctx context.Context, r *web.Request) web.Result { 471 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/UpdateDeliveryInfoAction") 472 defer span.End() 473 474 result := newResult() 475 form, success, err := cc.deliveryFormController.HandleFormAction(ctx, r) 476 result.Success = success 477 if err != nil { 478 result.SetError(err, "form_error") 479 return cc.responder.Data(result) 480 } 481 if form != nil { 482 result.Data = form.Data 483 result.DataValidationInfo = &form.ValidationInfo 484 } 485 cc.enrichResultWithCartInfos(ctx, &result) 486 return cc.responder.Data(result) 487 } 488 489 // UpdatePaymentSelectionAction to set / update the cart payment selection 490 // @Summary Update/set the PaymentSelection for the current cart 491 // @Tags Cart 492 // @Produce json 493 // @Success 200 {object} CartAPIResult 494 // @Failure 500 {object} CartAPIResult 495 // @Param gateway query string true "name of the payment gateway - e.g. 'offline'" 496 // @Param method query string true "name of the payment method - e.g. 'offlinepayment_cashondelivery'" 497 // @Router /api/v1/cart/payment-selection [put] 498 func (cc *CartAPIController) UpdatePaymentSelectionAction(ctx context.Context, r *web.Request) web.Result { 499 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/UpdatePaymentSelectionAction") 500 defer span.End() 501 502 result := newResult() 503 gateway, _ := r.Query1("gateway") 504 method, _ := r.Query1("method") 505 506 urlValues := make(url.Values) 507 urlValues["gateway"] = []string{gateway} 508 urlValues["method"] = []string{method} 509 newRequest := web.CreateRequest(web.RequestFromContext(ctx).Request(), web.SessionFromContext(ctx)) 510 newRequest.Request().Form = urlValues 511 512 form, success, err := cc.simplePaymentFormController.HandleFormAction(ctx, newRequest) 513 result.Success = success 514 if err != nil { 515 result.SetError(err, "form_error") 516 response := cc.responder.Data(result) 517 response.Status(500) 518 return response 519 } 520 if form != nil { 521 result.Data = form.Data 522 result.DataValidationInfo = &form.ValidationInfo 523 } 524 cc.enrichResultWithCartInfos(ctx, &result) 525 return cc.responder.Data(result) 526 } 527 528 func (cc *CartAPIController) enrichResultWithCartInfos(ctx context.Context, result *CartAPIResult) { 529 ctx, span := trace.StartSpan(ctx, "cart/CartAPIController/enrichResultWithCartInfos") 530 defer span.End() 531 532 session := web.SessionFromContext(ctx) 533 decoratedCart, err := cc.cartReceiverService.ViewDecoratedCart(ctx, session) 534 if err != nil { 535 result.SetError(err, "view_cart_error") 536 537 } 538 validationResult := cc.cartService.ValidateCart(ctx, session, decoratedCart) 539 result.CartTeaser = decoratedCart.Cart.GetCartTeaser() 540 result.CartValidationResult = &validationResult 541 } 542 543 // newResult factory to get new CartApiResult (with success true) 544 func newResult() CartAPIResult { 545 return CartAPIResult{ 546 Success: true, 547 } 548 } 549 550 // SetErrorByCode sets the error on the CartApiResult data and success to false 551 func (r *CartAPIResult) SetErrorByCode(message string, code string) *CartAPIResult { 552 r.Success = false 553 r.Error = &resultError{ 554 Message: message, 555 Code: code, 556 } 557 return r 558 } 559 560 // SetError updates the cart error field 561 func (r *CartAPIResult) SetError(err error, fallbackCode string) *CartAPIResult { 562 if e, ok := err.(messageCodeAvailable); ok { 563 fallbackCode = e.MessageCode() 564 } 565 return r.SetErrorByCode(err.Error(), fallbackCode) 566 }