Adds refunds of EnakPoint payments (docs/prd-point-coin.md F9, K7, Q13, PC-307). After a void or refund, onOrderRefunded now returns EnakPoint before taking earning back. For each EnakPoint payment of the order it returns everything on a void, and floor(refunded rupiah / the frozen point_value) when the payment itself was refunded, so a later change of the point value does not change how many come back and a remainder below one EnakPoint is lost. It never returns more than the payment used, and only what has not come back yet, so repeating is safe. PAYMENT_REFUND rows point at the PAYMENT they reverse, and the EnakPoint go back into lots with the expiry of the lots they were taken from, longest-lasting first (the 7-day extension waits on note N4). RefundOrder, which hands money back in cash or another method, is now limited to what was paid with other methods; the EnakPoint part has to be refunded through its own payment. That answers 400. Fixes earning reversal from PC-204: a refund of the EnakPoint part raised orders.refund_amount and so took earning back, although that part never earned. It is now left out of the refund the reversal uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
340 lines
15 KiB
Go
340 lines
15 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"apskel-pos-be/internal/appcontext"
|
|
"apskel-pos-be/internal/contract"
|
|
"apskel-pos-be/internal/processor"
|
|
"apskel-pos-be/internal/service"
|
|
"apskel-pos-be/internal/transformer"
|
|
"apskel-pos-be/internal/util"
|
|
"apskel-pos-be/internal/validator"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type OrderHandler struct {
|
|
orderService service.OrderService
|
|
validator validator.OrderValidator
|
|
transformer transformer.Transformer
|
|
}
|
|
|
|
func NewOrderHandler(
|
|
orderService service.OrderService,
|
|
validator validator.OrderValidator,
|
|
transformer transformer.Transformer,
|
|
) *OrderHandler {
|
|
return &OrderHandler{
|
|
orderService: orderService,
|
|
validator: validator,
|
|
transformer: transformer,
|
|
}
|
|
}
|
|
|
|
func (h *OrderHandler) CreateOrder(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
var req contract.CreateOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::CreateOrder", err.Error())}), "OrderHandler::CreateOrder")
|
|
return
|
|
}
|
|
|
|
req.UserID = contextInfo.UserID
|
|
modelReq := transformer.CreateOrderContractToModel(&req)
|
|
response, err := h.orderService.CreateOrder(ctx, modelReq, contextInfo.OrganizationID)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::CreateOrder", err.Error())}), "OrderHandler::CreateOrder")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.OrderModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::CreateOrder")
|
|
}
|
|
|
|
func (h *OrderHandler) GetOrderByID(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_id", "OrderHandler::GetOrderByID", err.Error())}), "OrderHandler::GetOrderByID")
|
|
return
|
|
}
|
|
|
|
response, err := h.orderService.GetOrderByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::GetOrderByID", err.Error())}), "OrderHandler::GetOrderByID")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.OrderModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::GetOrderByID")
|
|
}
|
|
|
|
func (h *OrderHandler) UpdateOrder(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_id", "OrderHandler::UpdateOrder", err.Error())}), "OrderHandler::UpdateOrder")
|
|
return
|
|
}
|
|
|
|
var req contract.UpdateOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::UpdateOrder", err.Error())}), "OrderHandler::UpdateOrder")
|
|
return
|
|
}
|
|
|
|
modelReq := transformer.UpdateOrderContractToModel(&req)
|
|
response, err := h.orderService.UpdateOrder(c.Request.Context(), id, modelReq)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::UpdateOrder", err.Error())}), "OrderHandler::UpdateOrder")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.OrderModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::UpdateOrder")
|
|
}
|
|
|
|
func (h *OrderHandler) AddToOrder(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_id", "OrderHandler::AddToOrder", err.Error())}), "OrderHandler::AddToOrder")
|
|
return
|
|
}
|
|
|
|
var req contract.AddToOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::AddToOrder", err.Error())}), "OrderHandler::AddToOrder")
|
|
return
|
|
}
|
|
|
|
modelReq := transformer.AddToOrderContractToModel(&req)
|
|
response, err := h.orderService.AddToOrder(c.Request.Context(), id, modelReq)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::AddToOrder", err.Error())}), "OrderHandler::AddToOrder")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.AddToOrderModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::AddToOrder")
|
|
}
|
|
|
|
func (h *OrderHandler) ListOrders(c *gin.Context) {
|
|
var query contract.ListOrdersQuery
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
if err := c.ShouldBindQuery(&query); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_query_parameters", "OrderHandler::ListOrders", err.Error())}), "OrderHandler::ListOrders")
|
|
return
|
|
}
|
|
|
|
modelReq := transformer.ListOrdersQueryToModel(&query)
|
|
if err := h.validator.Validate(modelReq); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::ListOrders", err.Error())}), "OrderHandler::ListOrders")
|
|
return
|
|
}
|
|
|
|
modelReq.OrganizationID = &contextInfo.OrganizationID
|
|
if modelReq.OutletID == nil && contextInfo.OutletID != uuid.Nil {
|
|
modelReq.OutletID = &contextInfo.OutletID
|
|
}
|
|
|
|
response, err := h.orderService.ListOrders(c.Request.Context(), modelReq)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::ListOrders", err.Error())}), "OrderHandler::ListOrders")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.ListOrdersModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::ListOrders")
|
|
}
|
|
|
|
func (h *OrderHandler) VoidOrder(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
var req contract.VoidOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::VoidOrder", err.Error())}), "OrderHandler::VoidOrder")
|
|
return
|
|
}
|
|
|
|
userID := contextInfo.UserID
|
|
if err := h.validator.Validate(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::VoidOrder", err.Error())}), "OrderHandler::VoidOrder")
|
|
return
|
|
}
|
|
|
|
modelReq := transformer.VoidOrderContractToModel(&req)
|
|
if err := h.orderService.VoidOrder(ctx, modelReq, userID); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::VoidOrder", err.Error())}), "OrderHandler::VoidOrder")
|
|
return
|
|
}
|
|
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(map[string]string{"message": "Order voided successfully"}), "OrderHandler::VoidOrder")
|
|
}
|
|
|
|
func (h *OrderHandler) RefundOrder(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_id", "OrderHandler::RefundOrder", err.Error())}), "OrderHandler::RefundOrder")
|
|
return
|
|
}
|
|
|
|
var req contract.RefundOrderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::RefundOrder", err.Error())}), "OrderHandler::RefundOrder")
|
|
return
|
|
}
|
|
|
|
userID := contextInfo.UserID
|
|
|
|
modelReq := transformer.RefundOrderContractToModel(&req)
|
|
if err := h.validator.Validate(modelReq); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::RefundOrder", err.Error())}), "OrderHandler::RefundOrder")
|
|
return
|
|
}
|
|
|
|
if err := h.orderService.RefundOrder(ctx, id, modelReq, userID); err != nil {
|
|
// Refusing to hand EnakPoint back as cash is a bad request, not a server fault.
|
|
if errors.Is(err, processor.ErrPointPaymentRejected) {
|
|
util.HandleResponse(c.Writer, c.Request, service.PointPaymentErrorResponse(err), "OrderHandler::RefundOrder")
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::RefundOrder", err.Error())}), "OrderHandler::RefundOrder")
|
|
return
|
|
}
|
|
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "OrderHandler::RefundOrder")
|
|
}
|
|
|
|
func (h *OrderHandler) CreatePayment(c *gin.Context) {
|
|
var req contract.CreatePaymentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
|
return
|
|
}
|
|
|
|
if err := h.validator.Validate(req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
|
return
|
|
}
|
|
|
|
modelReq := transformer.CreatePaymentContractToModel(&req)
|
|
|
|
response, err := h.orderService.CreatePayment(c.Request.Context(), modelReq)
|
|
if err != nil {
|
|
// A refused EnakPoint payment is the cashier's or customer's to fix, not a server fault.
|
|
if errors.Is(err, processor.ErrPointPaymentRejected) {
|
|
util.HandleResponse(c.Writer, c.Request, service.PointPaymentErrorResponse(err), "OrderHandler::CreatePayment")
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.PaymentModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::CreatePayment")
|
|
}
|
|
|
|
func (h *OrderHandler) RefundPayment(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
idStr := c.Param("id")
|
|
paymentID, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_id", "OrderHandler::RefundPayment", err.Error())}), "OrderHandler::RefundPayment")
|
|
return
|
|
}
|
|
|
|
var req contract.RefundPaymentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::RefundPayment", err.Error())}), "OrderHandler::RefundPayment")
|
|
return
|
|
}
|
|
|
|
userID := contextInfo.UserID
|
|
if userID == uuid.Nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("unauthorized", "OrderHandler::RefundPayment", "Invalid User ID in context")}), "OrderHandler::RefundPayment")
|
|
return
|
|
}
|
|
|
|
if err := h.validator.Validate(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::RefundPayment", err.Error())}), "OrderHandler::RefundPayment")
|
|
return
|
|
}
|
|
|
|
if err := h.orderService.RefundPayment(ctx, paymentID, req.RefundAmount, req.Reason, userID); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::RefundPayment", err.Error())}), "OrderHandler::RefundPayment")
|
|
return
|
|
}
|
|
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "OrderHandler::RefundPayment")
|
|
}
|
|
|
|
func (h *OrderHandler) SetOrderCustomer(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
orderIDStr := c.Param("id")
|
|
orderID, err := uuid.Parse(orderIDStr)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_id", "OrderHandler::SetOrderCustomer", "Invalid order ID")}), "OrderHandler::SetOrderCustomer")
|
|
return
|
|
}
|
|
|
|
var req contract.SetOrderCustomerRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
|
return
|
|
}
|
|
|
|
modelReq := transformer.SetOrderCustomerContractToModel(&req)
|
|
|
|
response, err := h.orderService.SetOrderCustomer(ctx, orderID, modelReq, contextInfo.OrganizationID)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.SetOrderCustomerModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::SetOrderCustomer")
|
|
}
|
|
|
|
func (h *OrderHandler) SplitBill(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
contextInfo := appcontext.FromGinContext(ctx)
|
|
|
|
var req contract.SplitBillRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
|
return
|
|
}
|
|
|
|
if err := h.validator.Validate(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
|
return
|
|
}
|
|
|
|
req.OrganizationID = contextInfo.OrganizationID
|
|
modelReq := transformer.SplitBillContractToModel(&req)
|
|
response, err := h.orderService.SplitBill(c.Request.Context(), modelReq)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
|
return
|
|
}
|
|
|
|
contractResp := transformer.SplitBillModelToContract(response)
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::SplitBill")
|
|
}
|