Spaces:
Runtime error
Runtime error
| package controller | |
| import ( | |
| "strconv" | |
| "strings" | |
| "github.com/QuantumNous/new-api/common" | |
| "github.com/QuantumNous/new-api/model" | |
| "github.com/QuantumNous/new-api/setting/ratio_setting" | |
| "github.com/gin-gonic/gin" | |
| "gorm.io/gorm" | |
| ) | |
| // ---- Shared types ---- | |
| type SubscriptionPlanDTO struct { | |
| Plan model.SubscriptionPlan `json:"plan"` | |
| } | |
| type BillingPreferenceRequest struct { | |
| BillingPreference string `json:"billing_preference"` | |
| } | |
| // ---- User APIs ---- | |
| func GetSubscriptionPlans(c *gin.Context) { | |
| var plans []model.SubscriptionPlan | |
| if err := model.DB.Where("enabled = ?", true).Order("sort_order desc, id desc").Find(&plans).Error; err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| result := make([]SubscriptionPlanDTO, 0, len(plans)) | |
| for _, p := range plans { | |
| result = append(result, SubscriptionPlanDTO{ | |
| Plan: p, | |
| }) | |
| } | |
| common.ApiSuccess(c, result) | |
| } | |
| func GetSubscriptionSelf(c *gin.Context) { | |
| userId := c.GetInt("id") | |
| settingMap, _ := model.GetUserSetting(userId, false) | |
| pref := common.NormalizeBillingPreference(settingMap.BillingPreference) | |
| // Get all subscriptions (including expired) | |
| allSubscriptions, err := model.GetAllUserSubscriptions(userId) | |
| if err != nil { | |
| allSubscriptions = []model.SubscriptionSummary{} | |
| } | |
| // Get active subscriptions for backward compatibility | |
| activeSubscriptions, err := model.GetAllActiveUserSubscriptions(userId) | |
| if err != nil { | |
| activeSubscriptions = []model.SubscriptionSummary{} | |
| } | |
| common.ApiSuccess(c, gin.H{ | |
| "billing_preference": pref, | |
| "subscriptions": activeSubscriptions, // all active subscriptions | |
| "all_subscriptions": allSubscriptions, // all subscriptions including expired | |
| }) | |
| } | |
| func UpdateSubscriptionPreference(c *gin.Context) { | |
| userId := c.GetInt("id") | |
| var req BillingPreferenceRequest | |
| if err := c.ShouldBindJSON(&req); err != nil { | |
| common.ApiErrorMsg(c, "参数错误") | |
| return | |
| } | |
| pref := common.NormalizeBillingPreference(req.BillingPreference) | |
| user, err := model.GetUserById(userId, true) | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| current := user.GetSetting() | |
| current.BillingPreference = pref | |
| user.SetSetting(current) | |
| if err := user.Update(false); err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| common.ApiSuccess(c, gin.H{"billing_preference": pref}) | |
| } | |
| // ---- Admin APIs ---- | |
| func AdminListSubscriptionPlans(c *gin.Context) { | |
| var plans []model.SubscriptionPlan | |
| if err := model.DB.Order("sort_order desc, id desc").Find(&plans).Error; err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| result := make([]SubscriptionPlanDTO, 0, len(plans)) | |
| for _, p := range plans { | |
| result = append(result, SubscriptionPlanDTO{ | |
| Plan: p, | |
| }) | |
| } | |
| common.ApiSuccess(c, result) | |
| } | |
| type AdminUpsertSubscriptionPlanRequest struct { | |
| Plan model.SubscriptionPlan `json:"plan"` | |
| } | |
| func AdminCreateSubscriptionPlan(c *gin.Context) { | |
| var req AdminUpsertSubscriptionPlanRequest | |
| if err := c.ShouldBindJSON(&req); err != nil { | |
| common.ApiErrorMsg(c, "参数错误") | |
| return | |
| } | |
| req.Plan.Id = 0 | |
| if strings.TrimSpace(req.Plan.Title) == "" { | |
| common.ApiErrorMsg(c, "套餐标题不能为空") | |
| return | |
| } | |
| if req.Plan.PriceAmount < 0 { | |
| common.ApiErrorMsg(c, "价格不能为负数") | |
| return | |
| } | |
| if req.Plan.PriceAmount > 9999 { | |
| common.ApiErrorMsg(c, "价格不能超过9999") | |
| return | |
| } | |
| if req.Plan.Currency == "" { | |
| req.Plan.Currency = "USD" | |
| } | |
| req.Plan.Currency = "USD" | |
| if req.Plan.DurationUnit == "" { | |
| req.Plan.DurationUnit = model.SubscriptionDurationMonth | |
| } | |
| if req.Plan.DurationValue <= 0 && req.Plan.DurationUnit != model.SubscriptionDurationCustom { | |
| req.Plan.DurationValue = 1 | |
| } | |
| if req.Plan.MaxPurchasePerUser < 0 { | |
| common.ApiErrorMsg(c, "购买上限不能为负数") | |
| return | |
| } | |
| if req.Plan.TotalAmount < 0 { | |
| common.ApiErrorMsg(c, "总额度不能为负数") | |
| return | |
| } | |
| req.Plan.UpgradeGroup = strings.TrimSpace(req.Plan.UpgradeGroup) | |
| if req.Plan.UpgradeGroup != "" { | |
| if _, ok := ratio_setting.GetGroupRatioCopy()[req.Plan.UpgradeGroup]; !ok { | |
| common.ApiErrorMsg(c, "升级分组不存在") | |
| return | |
| } | |
| } | |
| req.Plan.QuotaResetPeriod = model.NormalizeResetPeriod(req.Plan.QuotaResetPeriod) | |
| if req.Plan.QuotaResetPeriod == model.SubscriptionResetCustom && req.Plan.QuotaResetCustomSeconds <= 0 { | |
| common.ApiErrorMsg(c, "自定义重置周期需大于0秒") | |
| return | |
| } | |
| err := model.DB.Create(&req.Plan).Error | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| model.InvalidateSubscriptionPlanCache(req.Plan.Id) | |
| common.ApiSuccess(c, req.Plan) | |
| } | |
| func AdminUpdateSubscriptionPlan(c *gin.Context) { | |
| id, _ := strconv.Atoi(c.Param("id")) | |
| if id <= 0 { | |
| common.ApiErrorMsg(c, "无效的ID") | |
| return | |
| } | |
| var req AdminUpsertSubscriptionPlanRequest | |
| if err := c.ShouldBindJSON(&req); err != nil { | |
| common.ApiErrorMsg(c, "参数错误") | |
| return | |
| } | |
| if strings.TrimSpace(req.Plan.Title) == "" { | |
| common.ApiErrorMsg(c, "套餐标题不能为空") | |
| return | |
| } | |
| if req.Plan.PriceAmount < 0 { | |
| common.ApiErrorMsg(c, "价格不能为负数") | |
| return | |
| } | |
| if req.Plan.PriceAmount > 9999 { | |
| common.ApiErrorMsg(c, "价格不能超过9999") | |
| return | |
| } | |
| req.Plan.Id = id | |
| if req.Plan.Currency == "" { | |
| req.Plan.Currency = "USD" | |
| } | |
| req.Plan.Currency = "USD" | |
| if req.Plan.DurationUnit == "" { | |
| req.Plan.DurationUnit = model.SubscriptionDurationMonth | |
| } | |
| if req.Plan.DurationValue <= 0 && req.Plan.DurationUnit != model.SubscriptionDurationCustom { | |
| req.Plan.DurationValue = 1 | |
| } | |
| if req.Plan.MaxPurchasePerUser < 0 { | |
| common.ApiErrorMsg(c, "购买上限不能为负数") | |
| return | |
| } | |
| if req.Plan.TotalAmount < 0 { | |
| common.ApiErrorMsg(c, "总额度不能为负数") | |
| return | |
| } | |
| req.Plan.UpgradeGroup = strings.TrimSpace(req.Plan.UpgradeGroup) | |
| if req.Plan.UpgradeGroup != "" { | |
| if _, ok := ratio_setting.GetGroupRatioCopy()[req.Plan.UpgradeGroup]; !ok { | |
| common.ApiErrorMsg(c, "升级分组不存在") | |
| return | |
| } | |
| } | |
| req.Plan.QuotaResetPeriod = model.NormalizeResetPeriod(req.Plan.QuotaResetPeriod) | |
| if req.Plan.QuotaResetPeriod == model.SubscriptionResetCustom && req.Plan.QuotaResetCustomSeconds <= 0 { | |
| common.ApiErrorMsg(c, "自定义重置周期需大于0秒") | |
| return | |
| } | |
| err := model.DB.Transaction(func(tx *gorm.DB) error { | |
| // update plan (allow zero values updates with map) | |
| updateMap := map[string]interface{}{ | |
| "title": req.Plan.Title, | |
| "subtitle": req.Plan.Subtitle, | |
| "price_amount": req.Plan.PriceAmount, | |
| "currency": req.Plan.Currency, | |
| "duration_unit": req.Plan.DurationUnit, | |
| "duration_value": req.Plan.DurationValue, | |
| "custom_seconds": req.Plan.CustomSeconds, | |
| "enabled": req.Plan.Enabled, | |
| "sort_order": req.Plan.SortOrder, | |
| "stripe_price_id": req.Plan.StripePriceId, | |
| "creem_product_id": req.Plan.CreemProductId, | |
| "max_purchase_per_user": req.Plan.MaxPurchasePerUser, | |
| "total_amount": req.Plan.TotalAmount, | |
| "upgrade_group": req.Plan.UpgradeGroup, | |
| "quota_reset_period": req.Plan.QuotaResetPeriod, | |
| "quota_reset_custom_seconds": req.Plan.QuotaResetCustomSeconds, | |
| "updated_at": common.GetTimestamp(), | |
| } | |
| if err := tx.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Updates(updateMap).Error; err != nil { | |
| return err | |
| } | |
| return nil | |
| }) | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| model.InvalidateSubscriptionPlanCache(id) | |
| common.ApiSuccess(c, nil) | |
| } | |
| type AdminUpdateSubscriptionPlanStatusRequest struct { | |
| Enabled *bool `json:"enabled"` | |
| } | |
| func AdminUpdateSubscriptionPlanStatus(c *gin.Context) { | |
| id, _ := strconv.Atoi(c.Param("id")) | |
| if id <= 0 { | |
| common.ApiErrorMsg(c, "无效的ID") | |
| return | |
| } | |
| var req AdminUpdateSubscriptionPlanStatusRequest | |
| if err := c.ShouldBindJSON(&req); err != nil || req.Enabled == nil { | |
| common.ApiErrorMsg(c, "参数错误") | |
| return | |
| } | |
| if err := model.DB.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Update("enabled", *req.Enabled).Error; err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| model.InvalidateSubscriptionPlanCache(id) | |
| common.ApiSuccess(c, nil) | |
| } | |
| type AdminBindSubscriptionRequest struct { | |
| UserId int `json:"user_id"` | |
| PlanId int `json:"plan_id"` | |
| } | |
| func AdminBindSubscription(c *gin.Context) { | |
| var req AdminBindSubscriptionRequest | |
| if err := c.ShouldBindJSON(&req); err != nil || req.UserId <= 0 || req.PlanId <= 0 { | |
| common.ApiErrorMsg(c, "参数错误") | |
| return | |
| } | |
| msg, err := model.AdminBindSubscription(req.UserId, req.PlanId, "") | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| if msg != "" { | |
| common.ApiSuccess(c, gin.H{"message": msg}) | |
| return | |
| } | |
| common.ApiSuccess(c, nil) | |
| } | |
| // ---- Admin: user subscription management ---- | |
| func AdminListUserSubscriptions(c *gin.Context) { | |
| userId, _ := strconv.Atoi(c.Param("id")) | |
| if userId <= 0 { | |
| common.ApiErrorMsg(c, "无效的用户ID") | |
| return | |
| } | |
| subs, err := model.GetAllUserSubscriptions(userId) | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| common.ApiSuccess(c, subs) | |
| } | |
| type AdminCreateUserSubscriptionRequest struct { | |
| PlanId int `json:"plan_id"` | |
| } | |
| // AdminCreateUserSubscription creates a new user subscription from a plan (no payment). | |
| func AdminCreateUserSubscription(c *gin.Context) { | |
| userId, _ := strconv.Atoi(c.Param("id")) | |
| if userId <= 0 { | |
| common.ApiErrorMsg(c, "无效的用户ID") | |
| return | |
| } | |
| var req AdminCreateUserSubscriptionRequest | |
| if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 { | |
| common.ApiErrorMsg(c, "参数错误") | |
| return | |
| } | |
| msg, err := model.AdminBindSubscription(userId, req.PlanId, "") | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| if msg != "" { | |
| common.ApiSuccess(c, gin.H{"message": msg}) | |
| return | |
| } | |
| common.ApiSuccess(c, nil) | |
| } | |
| // AdminInvalidateUserSubscription cancels a user subscription immediately. | |
| func AdminInvalidateUserSubscription(c *gin.Context) { | |
| subId, _ := strconv.Atoi(c.Param("id")) | |
| if subId <= 0 { | |
| common.ApiErrorMsg(c, "无效的订阅ID") | |
| return | |
| } | |
| msg, err := model.AdminInvalidateUserSubscription(subId) | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| if msg != "" { | |
| common.ApiSuccess(c, gin.H{"message": msg}) | |
| return | |
| } | |
| common.ApiSuccess(c, nil) | |
| } | |
| // AdminDeleteUserSubscription hard-deletes a user subscription. | |
| func AdminDeleteUserSubscription(c *gin.Context) { | |
| subId, _ := strconv.Atoi(c.Param("id")) | |
| if subId <= 0 { | |
| common.ApiErrorMsg(c, "无效的订阅ID") | |
| return | |
| } | |
| msg, err := model.AdminDeleteUserSubscription(subId) | |
| if err != nil { | |
| common.ApiError(c, err) | |
| return | |
| } | |
| if msg != "" { | |
| common.ApiSuccess(c, gin.H{"message": msg}) | |
| return | |
| } | |
| common.ApiSuccess(c, nil) | |
| } | |