flamingo.me/flamingo-commerce/v3@v3.11.0/checkout/interfaces/controller/apicontroller.go (about) 1 package controller 2 3 import ( 4 "context" 5 "net/http" 6 "net/url" 7 8 "go.opencensus.io/trace" 9 10 "flamingo.me/flamingo-commerce/v3/cart/domain/cart" 11 placeorderDomain "flamingo.me/flamingo-commerce/v3/cart/domain/placeorder" 12 "flamingo.me/flamingo-commerce/v3/cart/domain/validation" 13 "flamingo.me/flamingo-commerce/v3/checkout/application" 14 "flamingo.me/flamingo-commerce/v3/checkout/domain/placeorder/process" 15 16 "flamingo.me/flamingo/v3/framework/flamingo" 17 "flamingo.me/flamingo/v3/framework/web" 18 19 cartApplication "flamingo.me/flamingo-commerce/v3/cart/application" 20 "flamingo.me/flamingo-commerce/v3/cart/domain/decorator" 21 "flamingo.me/flamingo-commerce/v3/checkout/application/placeorder" 22 ) 23 24 type ( 25 // APIController for checkout rest api 26 APIController struct { 27 responder *web.Responder 28 placeorderHandler *placeorder.Handler 29 cartService *cartApplication.CartService 30 logger flamingo.Logger 31 decoratedCartFactory *decorator.DecoratedCartFactory 32 } 33 34 // startPlaceOrderResult result of start place order 35 startPlaceOrderResult struct { 36 UUID string 37 } 38 39 // placeOrderContext infos 40 placeOrderContext struct { 41 Cart *cart.Cart 42 OrderInfos *placedOrderInfos 43 State string 44 StateData process.StateData 45 UUID string 46 FailedReason string 47 CartValidationResult *validation.Result 48 } 49 50 // placedOrderInfos infos 51 placedOrderInfos struct { 52 PaymentInfos []application.PlaceOrderPaymentInfo 53 PlacedOrderInfos []placeorderDomain.PlacedOrderInfo 54 Email string 55 PlacedDecoratedCart *decorator.DecoratedCart 56 } 57 58 // errorResponse format 59 errorResponse struct { 60 Code string 61 Message string 62 } // @name checkoutError 63 ) 64 65 // Inject dependencies 66 func (c *APIController) Inject( 67 responder *web.Responder, 68 placeorderHandler *placeorder.Handler, 69 cartService *cartApplication.CartService, 70 decoratedCartFactory *decorator.DecoratedCartFactory, 71 logger flamingo.Logger, 72 ) { 73 c.responder = responder 74 c.placeorderHandler = placeorderHandler 75 c.decoratedCartFactory = decoratedCartFactory 76 c.cartService = cartService 77 c.logger = logger.WithField(flamingo.LogKeyModule, "checkout").WithField(flamingo.LogKeyCategory, "apicontroller") 78 } 79 80 // StartPlaceOrderAction starts a new process 81 // @Summary Starts the place order process, which is a background process handling payment and rollbacks if required. 82 // @Tags Checkout 83 // @Produce json 84 // @Success 201 {object} startPlaceOrderResult "201 if new process was started" 85 // @Failure 500 {object} errorResponse 86 // @Failure 400 {object} errorResponse 87 // @Param returnURL query string true "the returnURL that should be used after an external payment flow" 88 // @Router /api/v1/checkout/placeorder [put] 89 func (c *APIController) StartPlaceOrderAction(ctx context.Context, r *web.Request) web.Result { 90 ctx, span := trace.StartSpan(ctx, "checkout/APIController/StartPlaceOrderAction") 91 defer span.End() 92 93 session := web.SessionFromContext(ctx) 94 cart, err := c.cartService.GetCartReceiverService().ViewCart(ctx, session) 95 if err != nil { 96 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 97 response.Status(http.StatusInternalServerError) 98 return response 99 } 100 returnURLRaw, err := r.Query1("returnURL") 101 if err != nil { 102 response := c.responder.Data(errorResponse{Code: "400", Message: "returnURL missing"}) 103 response.Status(http.StatusBadRequest) 104 return response 105 } 106 var returnURL *url.URL 107 returnURL, err = url.Parse(returnURLRaw) 108 if err != nil { 109 response := c.responder.Data(errorResponse{Code: "400", Message: err.Error()}) 110 response.Status(http.StatusBadRequest) 111 return response 112 } 113 114 startPlaceOrderCommand := placeorder.StartPlaceOrderCommand{Cart: *cart, ReturnURL: returnURL} 115 pctx, err := c.placeorderHandler.StartPlaceOrder(ctx, startPlaceOrderCommand) 116 if err != nil { 117 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 118 response.Status(http.StatusInternalServerError) 119 return response 120 } 121 response := c.responder.Data(startPlaceOrderResult{ 122 UUID: pctx.UUID, 123 }) 124 response.Status(http.StatusCreated) 125 return response 126 } 127 128 // CancelPlaceOrderAction cancels a running place order process 129 // @Summary Cancels a running place order process 130 // @Tags Checkout 131 // @Produce json 132 // @Success 200 {boolean} boolean 133 // @Failure 500 {object} errorResponse 134 // @Router /api/v1/checkout/placeorder/cancel [post] 135 func (c *APIController) CancelPlaceOrderAction(ctx context.Context, r *web.Request) web.Result { 136 ctx, span := trace.StartSpan(ctx, "checkout/APIController/CancelPlaceOrderAction") 137 defer span.End() 138 139 err := c.placeorderHandler.CancelPlaceOrder(ctx, placeorder.CancelPlaceOrderCommand{}) 140 if err != nil { 141 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 142 response.Status(http.StatusInternalServerError) 143 return response 144 } 145 return c.responder.Data(true) 146 } 147 148 // ClearPlaceOrderAction clears the last place order if in final state 149 // @Summary Clears the last placed order if in final state 150 // @Tags Checkout 151 // @Produce json 152 // @Success 200 {boolean} boolean 153 // @Failure 500 {object} errorResponse 154 // @Router /api/v1/checkout/placeorder [delete] 155 func (c *APIController) ClearPlaceOrderAction(ctx context.Context, r *web.Request) web.Result { 156 ctx, span := trace.StartSpan(ctx, "checkout/APIController/ClearPlaceOrderAction") 157 defer span.End() 158 159 err := c.placeorderHandler.ClearPlaceOrder(ctx) 160 if err != nil { 161 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 162 response.Status(http.StatusInternalServerError) 163 return response 164 } 165 return c.responder.Data(true) 166 } 167 168 // CurrentPlaceOrderContextAction returns the last saved context 169 // @Summary Returns the last saved context 170 // @Tags Checkout 171 // @Produce json 172 // @Success 200 {object} placeOrderContext 173 // @Failure 500 {object} errorResponse 174 // @Router /api/v1/checkout/placeorder [get] 175 func (c *APIController) CurrentPlaceOrderContextAction(ctx context.Context, _ *web.Request) web.Result { 176 ctx, span := trace.StartSpan(ctx, "checkout/APIController/CurrentPlaceOrderContextAction") 177 defer span.End() 178 179 pctx, err := c.placeorderHandler.CurrentContext(ctx) 180 if err != nil { 181 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 182 response.Status(http.StatusInternalServerError) 183 return response 184 } 185 186 return c.responder.Data(c.getPlaceOrderContext(ctx, pctx)) 187 } 188 189 func (c *APIController) getPlaceOrderContext(ctx context.Context, pctx *process.Context) placeOrderContext { 190 ctx, span := trace.StartSpan(ctx, "checkout/APIController/getPlaceOrderContext") 191 defer span.End() 192 193 var orderInfos *placedOrderInfos 194 if pctx.PlaceOrderInfo != nil { 195 decoratedCart := c.decoratedCartFactory.Create(ctx, pctx.Cart) 196 orderInfos = &placedOrderInfos{ 197 PaymentInfos: pctx.PlaceOrderInfo.PaymentInfos, 198 PlacedOrderInfos: pctx.PlaceOrderInfo.PlacedOrders, 199 Email: pctx.PlaceOrderInfo.ContactEmail, 200 PlacedDecoratedCart: decoratedCart, 201 } 202 } 203 204 var validationResult *validation.Result 205 var failedReason string 206 if pctx.FailedReason != nil { 207 failedReason = pctx.FailedReason.Reason() 208 if reason, ok := pctx.FailedReason.(process.CartValidationErrorReason); ok { 209 validationResult = &reason.ValidationResult 210 } 211 } 212 213 return placeOrderContext{ 214 Cart: &pctx.Cart, 215 OrderInfos: orderInfos, 216 State: pctx.CurrentStateName, 217 StateData: pctx.CurrentStateData, 218 FailedReason: failedReason, 219 CartValidationResult: validationResult, 220 UUID: pctx.UUID, 221 } 222 } 223 224 // RefreshPlaceOrderAction returns the current place order context and proceeds the process in a non blocking way 225 // @Summary Returns the current place order context and proceeds the process in a non blocking way 226 // @Tags Checkout 227 // @Produce json 228 // @Success 200 {object} placeOrderContext 229 // @Failure 500 {object} errorResponse 230 // @Router /api/v1/checkout/placeorder/refresh [post] 231 func (c *APIController) RefreshPlaceOrderAction(ctx context.Context, _ *web.Request) web.Result { 232 ctx, span := trace.StartSpan(ctx, "checkout/APIController/RefreshPlaceOrderAction") 233 defer span.End() 234 235 pctx, err := c.placeorderHandler.RefreshPlaceOrder(ctx, placeorder.RefreshPlaceOrderCommand{}) 236 if err != nil { 237 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 238 response.Status(http.StatusInternalServerError) 239 return response 240 } 241 return c.responder.Data(c.getPlaceOrderContext(ctx, pctx)) 242 } 243 244 // RefreshPlaceOrderBlockingAction proceeds the process and returns the place order context afterwards (blocking) 245 // @Summary Proceeds the process and returns the place order context afterwards (blocking) 246 // @Description This is useful to get the most recent place order context, for example after returning from an external payment provider 247 // @Tags Checkout 248 // @Produce json 249 // @Success 200 {object} placeOrderContext 250 // @Failure 500 {object} errorResponse 251 // @Router /api/v1/checkout/placeorder/refresh-blocking [post] 252 func (c *APIController) RefreshPlaceOrderBlockingAction(ctx context.Context, _ *web.Request) web.Result { 253 ctx, span := trace.StartSpan(ctx, "checkout/APIController/RefreshPlaceOrderBlockingAction") 254 defer span.End() 255 256 pctx, err := c.placeorderHandler.RefreshPlaceOrderBlocking(ctx, placeorder.RefreshPlaceOrderCommand{}) 257 if err != nil { 258 response := c.responder.Data(errorResponse{Code: "500", Message: err.Error()}) 259 response.Status(http.StatusInternalServerError) 260 return response 261 } 262 return c.responder.Data(c.getPlaceOrderContext(ctx, pctx)) 263 }