package handler import ( "context" "net/http" "strconv" "strings" "time" "gpt-plus/internal/db" "gpt-plus/pkg/provider/card" "github.com/gin-gonic/gin" ) func RegisterCardRoutes(api *gin.RouterGroup) { // Cards api.GET("/cards", ListCards) api.GET("/cards/active", GetActiveCard) api.GET("/cards/stats", GetCardStats) api.POST("/cards", AddCard) api.PUT("/cards/:id/activate", ActivateCard) api.PUT("/cards/:id/status", UpdateCardStatus) api.DELETE("/cards/:id", DeleteCard) // Card codes api.GET("/card-codes", ListCardCodes) api.GET("/card-codes/stats", GetCardCodeStats) api.POST("/card-codes/import", ImportCardCodes) api.POST("/card-codes/redeem", RedeemCardCode) api.DELETE("/card-codes/:id", DeleteCardCode) } func ListCards(c *gin.Context) { d := db.GetDB() query := d.Model(&db.Card{}).Order("created_at DESC") if status := c.Query("status"); status != "" { query = query.Where("status = ?", status) } p := db.PaginationParams{ Page: intQuery(c, "page", 1), Size: intQuery(c, "size", 20), } var cards []db.Card result, err := db.Paginate(query, p, &cards) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } for i := range cards { number, _ := db.Decrypt(cards[i].NumberEnc) cards[i].NumberLast4 = number cvc, _ := db.Decrypt(cards[i].CVCEnc) cards[i].CVCPlain = cvc } result.Items = cards c.JSON(http.StatusOK, result) } func GetActiveCard(c *gin.Context) { d := db.GetDB() var card db.Card if err := d.Where("status = ?", "active").First(&card).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "无激活的卡片"}) return } number, _ := db.Decrypt(card.NumberEnc) card.NumberLast4 = number cvc, _ := db.Decrypt(card.CVCEnc) card.CVCPlain = cvc c.JSON(http.StatusOK, card) } func GetCardStats(c *gin.Context) { c.JSON(http.StatusOK, db.GetCardStats(db.GetDB())) } type addCardRequest struct { Number string `json:"number" binding:"required"` CVC string `json:"cvc" binding:"required"` ExpMonth string `json:"exp_month" binding:"required"` ExpYear string `json:"exp_year" binding:"required"` Name string `json:"name"` Country string `json:"country"` } func AddCard(c *gin.Context) { var req addCardRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } numberEnc, _ := db.Encrypt(req.Number) cvcEnc, _ := db.Encrypt(req.CVC) maxBinds := 0 // 0 = unlimited var cfg db.SystemConfig if db.GetDB().Where("key = ?", "card.max_binds").First(&cfg).Error == nil { maxBinds, _ = strconv.Atoi(cfg.Value) } card := &db.Card{ NumberHash: db.HashSHA256(req.Number), NumberEnc: numberEnc, CVCEnc: cvcEnc, ExpMonth: req.ExpMonth, ExpYear: req.ExpYear, Name: req.Name, Country: req.Country, Source: "manual", Status: "available", MaxBinds: maxBinds, } if err := db.GetDB().Create(card).Error; err != nil { if strings.Contains(err.Error(), "UNIQUE") { c.JSON(http.StatusConflict, gin.H{"error": "该卡号已存在"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{"id": card.ID, "message": "卡片已添加"}) } func ActivateCard(c *gin.Context) { id := c.Param("id") d := db.GetDB() // Deactivate current active card d.Model(&db.Card{}).Where("status = ?", "active").Update("status", "available") var card db.Card if err := d.First(&card, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "卡片不存在"}) return } card.Status = "active" d.Save(&card) c.JSON(http.StatusOK, gin.H{"message": "已激活"}) } func UpdateCardStatus(c *gin.Context) { id := c.Param("id") var req struct { Status string `json:"status" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Status != "disabled" && req.Status != "available" { c.JSON(http.StatusBadRequest, gin.H{"error": "状态只能为 disabled 或 available"}) return } if err := db.GetDB().Model(&db.Card{}).Where("id = ?", id).Update("status", req.Status).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "状态已更新"}) } func DeleteCard(c *gin.Context) { if err := db.GetDB().Delete(&db.Card{}, c.Param("id")).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "已删除"}) } // Card Codes func ListCardCodes(c *gin.Context) { d := db.GetDB() query := d.Model(&db.CardCode{}).Order("created_at DESC") if status := c.Query("status"); status != "" { query = query.Where("status = ?", status) } p := db.PaginationParams{Page: intQuery(c, "page", 1), Size: intQuery(c, "size", 20)} var codes []db.CardCode result, err := db.Paginate(query, p, &codes) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, result) } func GetCardCodeStats(c *gin.Context) { c.JSON(http.StatusOK, db.GetCardCodeStats(db.GetDB())) } func ImportCardCodes(c *gin.Context) { var req struct { Codes []string `json:"codes" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } d := db.GetDB() var count int for _, code := range req.Codes { code = strings.TrimSpace(code) if code == "" { continue } cc := &db.CardCode{Code: code, Status: "unused"} if d.Create(cc).Error == nil { count++ } } c.JSON(http.StatusOK, gin.H{"imported": count}) } func RedeemCardCode(c *gin.Context) { var req struct { ID uint `json:"id"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } d := db.GetDB() var code db.CardCode if err := d.First(&code, req.ID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "卡密不存在"}) return } if code.Status != "unused" { c.JSON(http.StatusBadRequest, gin.H{"error": "该卡密状态不是 unused"}) return } code.Status = "redeeming" d.Save(&code) // Redeem in background go func() { var cfgBase db.SystemConfig apiBase := "" if db.GetDB().Where("key = ?", "card.api_base_url").First(&cfgBase).Error == nil { apiBase = cfgBase.Value } if apiBase == "" { code.Status = "failed" code.Error = "开卡 API 地址未配置" d.Save(&code) return } apiProv, err := card.NewAPIProvider(card.APIProviderConfig{ BaseURL: apiBase, Codes: []string{code.Code}, PoolCfg: card.PoolConfig{MultiBind: true, MaxBinds: 999}, }) if err != nil { code.Status = "failed" code.Error = err.Error() d.Save(&code) return } cardInfo, err := apiProv.GetCard(context.Background()) if err != nil { code.Status = "failed" code.Error = err.Error() d.Save(&code) return } numberEnc, _ := db.Encrypt(cardInfo.Number) cvcEnc, _ := db.Encrypt(cardInfo.CVC) newCard := &db.Card{ NumberHash: db.HashSHA256(cardInfo.Number), NumberEnc: numberEnc, CVCEnc: cvcEnc, ExpMonth: cardInfo.ExpMonth, ExpYear: cardInfo.ExpYear, Name: cardInfo.Name, Country: cardInfo.Country, Source: "api", CardCodeID: &code.ID, Status: "available", MaxBinds: 1, } d.Create(newCard) now := time.Now() code.Status = "redeemed" code.CardID = &newCard.ID code.RedeemedAt = &now d.Save(&code) }() c.JSON(http.StatusOK, gin.H{"message": "兑换已开始"}) } func DeleteCardCode(c *gin.Context) { if err := db.GetDB().Delete(&db.CardCode{}, c.Param("id")).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "已删除"}) } func intQuery(c *gin.Context, key string, def int) int { v := c.Query(key) if v == "" { return def } n, err := strconv.Atoi(v) if err != nil { return def } return n }