package handler import ( "context" "encoding/json" "net/http" "strconv" "strings" "sync" "time" "soul-api/internal/database" "soul-api/internal/model" "soul-api/internal/wechat" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // OrdersList GET /api/orders(带用户昵称/头像/手机号,分销佣金按配置比例计算;支持分页 page、pageSize,筛选 status,搜索 search) func OrdersList(c *gin.Context) { db := database.DB() page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) statusFilter := c.Query("status") search := strings.TrimSpace(c.Query("search")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 10 } // 预加载 referral_config,避免订单循环内 N+1 查询 var refCfgRow model.SystemConfig refCfg := (*model.SystemConfig)(nil) if err := db.Where("config_key = ?", "referral_config").First(&refCfgRow).Error; err == nil { refCfg = &refCfgRow } // 构建带筛选的查询(count 与 list 共用条件) applyOrdersFilter := func(q *gorm.DB) *gorm.DB { if statusFilter != "" && statusFilter != "all" { if statusFilter == "completed" { q = q.Where("status IN ?", []string{"paid", "completed"}) } else { q = q.Where("status = ?", statusFilter) } } if search != "" { pattern := "%" + search + "%" q = q.Where("order_sn LIKE ? OR id LIKE ? OR user_id IN (SELECT id FROM users WHERE COALESCE(nickname,'') LIKE ? OR COALESCE(phone,'') LIKE ? OR id LIKE ?)", pattern, pattern, pattern, pattern, pattern) } return q } var total int64 var totalRevenue, todayRevenue float64 var orders []model.Order var ordersErr error var wg sync.WaitGroup // 并行:count、营收统计、订单列表 wg.Add(3) go func() { defer wg.Done() applyOrdersFilter(db.Model(&model.Order{})).Count(&total) }() go func() { defer wg.Done() db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)"). Where("status IN ?", []string{"paid", "completed"}).Scan(&totalRevenue) todayStart := time.Now().Truncate(24 * time.Hour) todayEnd := todayStart.Add(24 * time.Hour) db.Model(&model.Order{}).Select("COALESCE(SUM(amount), 0)"). Where("status IN ? AND created_at >= ? AND created_at < ?", []string{"paid", "completed"}, todayStart, todayEnd). Scan(&todayRevenue) }() go func() { defer wg.Done() query := applyOrdersFilter(db.Model(&model.Order{})) ordersErr = query.Order("created_at DESC"). Offset((page - 1) * pageSize). Limit(pageSize). Find(&orders).Error }() wg.Wait() if ordersErr != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": ordersErr.Error(), "orders": []interface{}{}, "total": 0}) return } totalPages := int(total) / pageSize if int(total)%pageSize > 0 { totalPages++ } if len(orders) == 0 { c.JSON(http.StatusOK, gin.H{ "success": true, "orders": []interface{}{}, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, }) return } // 收集订单中的 user_id、referrer_id,查用户信息 userIDs := make(map[string]bool) for _, o := range orders { if o.UserID != "" { userIDs[o.UserID] = true } if o.ReferrerID != nil && *o.ReferrerID != "" { userIDs[*o.ReferrerID] = true } } ids := make([]string, 0, len(userIDs)) for id := range userIDs { ids = append(ids, id) } var users []model.User if len(ids) > 0 { db.Where("id IN ?", ids).Find(&users) } userMap := make(map[string]*model.User) for i := range users { userMap[users[i].ID] = &users[i] } getStr := func(s *string) string { if s == nil || *s == "" { return "" } return *s } out := make([]gin.H, 0, len(orders)) for _, o := range orders { // 序列化订单为基础字段 b, _ := json.Marshal(o) var m map[string]interface{} _ = json.Unmarshal(b, &m) // 用户信息 if u := userMap[o.UserID]; u != nil { m["userNickname"] = getStr(u.Nickname) m["userPhone"] = getStr(u.Phone) m["userAvatar"] = getStr(u.Avatar) } else { m["userNickname"] = "" m["userPhone"] = "" m["userAvatar"] = "" } // 推荐人信息 if o.ReferrerID != nil && *o.ReferrerID != "" { if u := userMap[*o.ReferrerID]; u != nil { m["referrerNickname"] = getStr(u.Nickname) m["referrerCode"] = getStr(u.ReferralCode) } } // 分销佣金:仅对已支付且存在推荐人的订单,按 computeOrderCommission(会员 20%/10%,内容 90%) status := getStr(o.Status) if status == "paid" && o.ReferrerID != nil && *o.ReferrerID != "" { var refUser *model.User if u := userMap[*o.ReferrerID]; u != nil { refUser = u } m["referrerEarnings"] = computeOrderCommission(db, &o, refUser, refCfg) } else { m["referrerEarnings"] = nil } out = append(out, m) } c.JSON(http.StatusOK, gin.H{ "success": true, "orders": out, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, "totalRevenue": totalRevenue, "todayRevenue": todayRevenue, }) } // MiniprogramOrders GET /api/miniprogram/orders 小程序-当前用户订单列表(按 userId 过滤,返回 data) func MiniprogramOrders(c *gin.Context) { userID := c.Query("userId") if userID == "" { c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}}) return } db := database.DB() var orders []model.Order if err := db.Where("user_id = ?", userID).Order("created_at DESC").Find(&orders).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}}) return } out := make([]gin.H, 0, len(orders)) for _, o := range orders { desc := "" if o.Description != nil { desc = *o.Description } productID := "" if o.ProductID != nil { productID = *o.ProductID } status := "created" if o.Status != nil { status = *o.Status } out = append(out, gin.H{ "id": o.ID, "order_sn": o.OrderSN, "user_id": o.UserID, "product_id": productID, "product_type": o.ProductType, "product_name": desc, "section_id": productID, "amount": o.Amount, "status": status, "created_at": o.CreatedAt, }) } c.JSON(http.StatusOK, gin.H{"success": true, "data": out}) } // AdminOrderRefund PUT /api/admin/orders/refund 管理端-订单退款(仅支持已支付订单,调用微信支付退款) func AdminOrderRefund(c *gin.Context) { var req struct { OrderSn string `json:"orderSn" binding:"required"` Reason string `json:"reason"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少订单号"}) return } db := database.DB() var order model.Order if err := db.Where("order_sn = ?", req.OrderSn).First(&order).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "订单不存在"}) return } status := "" if order.Status != nil { status = *order.Status } if status != "paid" && status != "completed" { c.JSON(http.StatusOK, gin.H{"success": false, "error": "仅支持已支付订单退款"}) return } transactionID := "" if order.TransactionID != nil { transactionID = *order.TransactionID } if transactionID == "" { c.JSON(http.StatusOK, gin.H{"success": false, "error": "订单缺少微信支付单号,无法退款"}) return } totalCents := int(order.Amount * 100) if totalCents < 1 { totalCents = 1 } if err := wechat.RefundOrder(context.Background(), order.OrderSN, transactionID, totalCents, req.Reason); err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "微信退款失败: " + err.Error()}) return } refunded := "refunded" updates := map[string]interface{}{"status": refunded} if req.Reason != "" { updates["refund_reason"] = req.Reason } if err := db.Model(&order).Updates(updates).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "退款成功但更新订单状态失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "退款成功"}) }