Files
admin ea8ffb6c74 fix: 订阅模式登录验证、永久会员类型区分、动态代码HTTP返回值修复、侧边栏滚动位置保持
- 修复订阅模式登录时错误检查余额的问题
- 区分无限余额和永久订阅两种永久会员类型
- 修复动态代码HTTP请求返回值在JS中无法正确访问的问题
- 添加侧边栏滚动位置保持功能
- 移除developer角色相关代码,统一使用admin
- 添加缺失的i18n翻译key
2026-05-01 16:39:31 +08:00

224 lines
6.5 KiB
Go

package app
import (
"fmt"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/response"
"github.com/gin-gonic/gin"
)
func SetupPaymentRoutes(r *gin.RouterGroup) {
r.POST("/recharge", handleAppRecharge)
r.POST("/trial", handleAppTrial)
}
func handleAppRecharge(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Username string `json:"username"`
CardKey string `json:"card_key"`
DeviceID string `json:"device_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if blocked, reason := checkRiskControl(c, app.ID, req.DeviceID, req.Username); blocked {
response.Error(c, 403, reason)
return
}
var card model.Card
if err := database.DB.Preload("CardType").Where("card_key = ?", req.CardKey).First(&card).Error; err != nil {
service.LogVerification(c, &app.ID, nil, "recharge_failed", fmt.Sprintf("充值失败: 卡密不存在 - %s", req.CardKey), "", fmt.Errorf("卡密不存在"))
response.Error(c, 404, "卡密不存在")
return
}
fmt.Printf("[DEBUG] 卡密信息: ID=%d, CardKey=%s, Status=%s, CardTypeID=%d\n", card.ID, card.CardKey, card.Status, card.CardTypeID)
fmt.Printf("[DEBUG] 卡类信息: ID=%d, Name=%s, Value=%f, Price=%f\n", card.CardType.ID, card.CardType.Name, card.CardType.Value, card.CardType.Price)
if card.Status != "unused" {
service.LogVerification(c, &app.ID, nil, "recharge_failed", fmt.Sprintf("充值失败: 卡密已使用 - %s", req.CardKey), "", fmt.Errorf("卡密已使用"))
response.Error(c, 400, "卡密已使用")
return
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&user).Error; err != nil {
service.LogVerification(c, &app.ID, nil, "recharge_failed", fmt.Sprintf("充值失败: 用户不存在 - %s", req.Username), "", fmt.Errorf("用户不存在"))
response.Error(c, 404, "用户不存在")
return
}
if user.Balance == -1 {
service.LogVerification(c, &app.ID, &user.ID, "recharge_failed", fmt.Sprintf("充值失败: 用户已是永久会员 - %s", user.Username), "", fmt.Errorf("该用户已是永久会员,无法再次充值"))
response.Error(c, 400, "该用户已是永久会员,无法再次充值")
return
}
fmt.Printf("[DEBUG] 充值前用户信息: ID=%d, Username=%s, Balance=%f, ExpiryAt=%v\n", user.ID, user.Username, user.Balance, user.ExpiryAt)
tx := database.DB.Begin()
card.Status = "used"
card.AppUserID = &user.ID
now := time.Now()
card.UsedAt = &now
if err := tx.Save(&card).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "充值失败")
return
}
user.IsTrialUser = false
if card.CardType.Value == -1 {
if card.CardType.RechargeType == "subscription" {
permanentExpiry := time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
user.ExpiryAt = &permanentExpiry
user.Balance = -1
} else {
user.Balance = -1
user.ExpiryAt = nil
}
} else {
switch card.CardType.RechargeType {
case "subscription":
var baseTime time.Time
if user.ExpiryAt != nil && user.ExpiryAt.After(now) {
baseTime = *user.ExpiryAt
} else {
baseTime = now
}
var duration time.Duration
switch card.CardType.ValueUnit {
case "minute":
duration = time.Duration(card.CardType.Value) * time.Minute
case "hour":
duration = time.Duration(card.CardType.Value) * time.Hour
case "day":
duration = time.Duration(card.CardType.Value) * 24 * time.Hour
case "month":
duration = time.Duration(card.CardType.Value) * 30 * 24 * time.Hour
case "year":
duration = time.Duration(card.CardType.Value) * 365 * 24 * time.Hour
default:
duration = time.Duration(card.CardType.Value) * time.Second
}
newExpiry := baseTime.Add(duration)
user.ExpiryAt = &newExpiry
case "balance":
fallthrough
default:
user.Balance += card.CardType.Value
}
}
fmt.Printf("[DEBUG] 充值后用户信息: Balance=%f, ExpiryAt=%v (CardType.Value=%f, BillingType=%s)\n", user.Balance, user.ExpiryAt, card.CardType.Value, app.BillingType)
if err := tx.Save(&user).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "更新用户信息失败")
return
}
rechargeRecord := model.RechargeRecord{
UserID: user.ID,
OrderNo: generateOrderNo("R"),
CardID: &card.ID,
CardCode: card.CardKey,
Amount: card.CardType.Price,
Status: "success",
PaymentType: "card",
Remark: "卡密充值 - " + card.CardType.Name,
}
if err := tx.Create(&rechargeRecord).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "创建充值记录失败")
return
}
if err := tx.Commit().Error; err != nil {
response.Error(c, 500, "充值失败")
return
}
service.LogVerification(c, &app.ID, &user.ID, "recharge", fmt.Sprintf("用户充值: %s, 卡密: %s, 金额: %.2f", user.Username, card.CardKey, card.CardType.Price), "", nil)
response.SuccessWithMessage(c, "充值成功", gin.H{
"message": "充值成功",
"value": card.CardType.Value,
})
}
func handleAppTrial(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
if !app.EnableTrial {
response.Error(c, 400, "该应用不支持试用")
return
}
var req struct {
UserID uint `json:"user_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var user model.AppUser
if err := database.DB.First(&user, req.UserID).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
response.SuccessWithMessage(c, "试用成功", gin.H{
"message": "试用成功",
"trial_balance": app.TrialBalance,
})
}
func generateOrderNo(prefix string) string {
return prefix + time.Now().Format("20060102150405") + randomString(6)
}
func randomString(length int) string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[time.Now().Nanosecond()%len(charset)]
time.Sleep(1 * time.Nanosecond)
}
return string(b)
}