feat: 用户管理页面添加充值功能
- 后端新增 POST /app-users/:id/recharge API - 前端新增独立的用户充值页面 (/admin/users/:id/recharge) - 充值页面风格与添加用户/编辑用户页面一致 - 支持选择卡密类型和数量进行充值 - 永久会员显示提示不允许充值 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ func SetupUserRoutes(r *gin.RouterGroup) {
|
||||
appUsers.GET("/:id/devices", handleGetUserDevices)
|
||||
appUsers.DELETE("/:id/devices/:deviceId", HandleUnbindDevice)
|
||||
appUsers.PUT("/:id/expiry", handleUpdateExpiry)
|
||||
appUsers.POST("/:id/recharge", handleRechargeUser)
|
||||
appUsers.POST("/batch/status", handleBatchUpdateStatus)
|
||||
appUsers.DELETE("/batch", handleBatchDelete)
|
||||
}
|
||||
@@ -885,3 +886,171 @@ func handleBatchDelete(c *gin.Context) {
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleRechargeUser(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
var req struct {
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
CardQuantity int `json:"card_quantity"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.CardTypeID == 0 {
|
||||
response.Error(c, 400, "请选择卡密类型")
|
||||
return
|
||||
}
|
||||
|
||||
if req.CardQuantity < 1 {
|
||||
req.CardQuantity = 1
|
||||
}
|
||||
if req.CardQuantity > 100 {
|
||||
response.Error(c, 400, "卡密数量不能超过100")
|
||||
return
|
||||
}
|
||||
|
||||
var appUser model.AppUser
|
||||
if err := database.DB.First(&appUser, id).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, appUser.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限为该用户充值")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if appUser.Balance == -1 {
|
||||
response.Error(c, 400, "该用户为永久会员,无需充值")
|
||||
return
|
||||
}
|
||||
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Where("id = ? AND application_id = ?", req.CardTypeID, app.ID).First(&cardType).Error; err != nil {
|
||||
response.Error(c, 400, "卡密类型不存在或不属于该应用")
|
||||
return
|
||||
}
|
||||
|
||||
tx := database.DB.Begin()
|
||||
|
||||
now := time.Now()
|
||||
var cards []model.Card
|
||||
|
||||
for i := 0; i < req.CardQuantity; i++ {
|
||||
cardKey := "CK" + utils.GenerateRandomString(16)
|
||||
card := model.Card{
|
||||
ApplicationID: app.ID,
|
||||
CardTypeID: cardType.ID,
|
||||
CardKey: cardKey,
|
||||
CreatorID: userID,
|
||||
AppUserID: &appUser.ID,
|
||||
Status: "used",
|
||||
}
|
||||
card.UsedAt = &now
|
||||
|
||||
if err := tx.Create(&card).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "生成卡密失败")
|
||||
return
|
||||
}
|
||||
|
||||
cards = append(cards, card)
|
||||
}
|
||||
|
||||
appUser.IsTrialUser = false
|
||||
|
||||
if cardType.Value == -1 {
|
||||
if cardType.RechargeType == "subscription" {
|
||||
permanentExpiry := time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
appUser.ExpiryAt = &permanentExpiry
|
||||
appUser.Balance = -1
|
||||
} else {
|
||||
appUser.Balance = -1
|
||||
appUser.ExpiryAt = nil
|
||||
}
|
||||
} else {
|
||||
switch cardType.RechargeType {
|
||||
case "subscription":
|
||||
var baseTime time.Time
|
||||
if appUser.ExpiryAt != nil && appUser.ExpiryAt.After(now) {
|
||||
baseTime = *appUser.ExpiryAt
|
||||
} else {
|
||||
baseTime = now
|
||||
}
|
||||
var duration time.Duration
|
||||
switch cardType.ValueUnit {
|
||||
case "minute":
|
||||
duration = time.Duration(cardType.Value) * time.Minute
|
||||
case "hour":
|
||||
duration = time.Duration(cardType.Value) * time.Hour
|
||||
case "day":
|
||||
duration = time.Duration(cardType.Value) * 24 * time.Hour
|
||||
case "month":
|
||||
duration = time.Duration(cardType.Value) * 30 * 24 * time.Hour
|
||||
case "year":
|
||||
duration = time.Duration(cardType.Value) * 365 * 24 * time.Hour
|
||||
default:
|
||||
duration = time.Duration(cardType.Value) * time.Second
|
||||
}
|
||||
for i := 0; i < req.CardQuantity; i++ {
|
||||
newExpiry := baseTime.Add(duration)
|
||||
baseTime = newExpiry
|
||||
}
|
||||
appUser.ExpiryAt = &baseTime
|
||||
case "balance":
|
||||
appUser.Balance += cardType.Value * float64(req.CardQuantity)
|
||||
default:
|
||||
appUser.Balance += cardType.Value * float64(req.CardQuantity)
|
||||
}
|
||||
}
|
||||
|
||||
for _, card := range cards {
|
||||
rechargeRecord := model.RechargeRecord{
|
||||
UserID: appUser.ID,
|
||||
OrderNo: generateUserOrderNo("R"),
|
||||
CardID: &card.ID,
|
||||
CardCode: card.CardKey,
|
||||
Amount: cardType.Price,
|
||||
Status: "success",
|
||||
PaymentType: "card",
|
||||
Remark: fmt.Sprintf("管理员充值 - %s x1", cardType.Name),
|
||||
}
|
||||
|
||||
if err := tx.Create(&rechargeRecord).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "创建充值记录失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Save(&appUser).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "充值失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
response.Error(c, 500, "充值失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "update", "app_user", &appUser.ID, fmt.Sprintf("为用户充值: %s, 卡密类型: %s x%d", appUser.Username, cardType.Name, req.CardQuantity), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"user": appUser,
|
||||
"cards": cards,
|
||||
"card_type": cardType,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user