diff --git a/internal/handler/admin/order/activateOrderHandler.go b/internal/handler/admin/order/activateOrderHandler.go new file mode 100644 index 0000000..9493231 --- /dev/null +++ b/internal/handler/admin/order/activateOrderHandler.go @@ -0,0 +1,25 @@ +package order + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/order" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +func ActivateOrderHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.ActivateOrderRequest + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := order.NewActivateOrderLogic(c.Request.Context(), svcCtx) + err := l.ActivateOrder(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 842fe14..73f6b5b 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -286,6 +286,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Update order status adminOrderGroupRouter.PUT("/status", adminOrder.UpdateOrderStatusHandler(serverCtx)) + + // Manually activate order + adminOrderGroupRouter.POST("/activate", adminOrder.ActivateOrderHandler(serverCtx)) } adminPaymentGroupRouter := router.Group("/v1/admin/payment") diff --git a/internal/logic/admin/order/activateOrderLogic.go b/internal/logic/admin/order/activateOrderLogic.go new file mode 100644 index 0000000..40d50bb --- /dev/null +++ b/internal/logic/admin/order/activateOrderLogic.go @@ -0,0 +1,68 @@ +package order + +import ( + "context" + "encoding/json" + + "github.com/hibiken/asynq" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/xerr" + "github.com/pkg/errors" + queue "github.com/perfect-panel/server/queue/types" +) + +type ActivateOrderLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewActivateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ActivateOrderLogic { + return &ActivateOrderLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ActivateOrderLogic) ActivateOrder(req *types.ActivateOrderRequest) error { + info, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) + if err != nil { + l.Errorw("[ActivateOrder] FindOneByOrderNo error", logger.Field("error", err.Error()), logger.Field("orderNo", req.OrderNo)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneByOrderNo error: %v", err.Error()) + } + + // 只允许对 pending(1) 或 closed(3) 的订单手动激活 + if info.Status != 1 && info.Status != 3 { + l.Infow("[ActivateOrder] order status not eligible", + logger.Field("orderNo", req.OrderNo), + logger.Field("status", info.Status), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order status %d not eligible for manual activation", info.Status) + } + + // 强制更新为已支付状态(绕过状态守卫) + err = l.svcCtx.DB.Model(info).Where("order_no = ?", req.OrderNo).Update("status", 2).Error + if err != nil { + l.Errorw("[ActivateOrder] update status to paid failed", logger.Field("error", err.Error()), logger.Field("orderNo", req.OrderNo)) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update order status failed: %v", err.Error()) + } + + // enqueue 激活任务 + payload := queue.ForthwithActivateOrderPayload{OrderNo: info.OrderNo} + p, _ := json.Marshal(payload) + task := asynq.NewTask(queue.ForthwithActivateOrder, p) + _, err = l.svcCtx.Queue.EnqueueContext(l.ctx, task) + if err != nil { + l.Errorw("[ActivateOrder] enqueue error", logger.Field("error", err.Error()), logger.Field("orderNo", req.OrderNo)) + return errors.Wrapf(xerr.NewErrCode(xerr.QueueEnqueueError), "enqueue error: %v", err.Error()) + } + + l.Infow("[ActivateOrder] order manually activated", + logger.Field("orderNo", req.OrderNo), + logger.Field("previousStatus", info.Status), + ) + return nil +} diff --git a/internal/types/types.go b/internal/types/types.go index 0895690..6dece23 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -2745,6 +2745,10 @@ type UpdateOrderStatusRequest struct { TradeNo string `json:"trade_no,omitempty"` } +type ActivateOrderRequest struct { + OrderNo string `json:"order_no" validate:"required"` +} + type UpdatePaymentMethodRequest struct { Id int64 `json:"id" validate:"required"` Name string `json:"name" validate:"required"`