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  }