subscription.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package controller
  2. import (
  3. "strconv"
  4. "strings"
  5. "github.com/QuantumNous/new-api/common"
  6. "github.com/QuantumNous/new-api/model"
  7. "github.com/QuantumNous/new-api/setting/ratio_setting"
  8. "github.com/gin-gonic/gin"
  9. "gorm.io/gorm"
  10. )
  11. // ---- Shared types ----
  12. type SubscriptionPlanDTO struct {
  13. Plan model.SubscriptionPlan `json:"plan"`
  14. }
  15. type BillingPreferenceRequest struct {
  16. BillingPreference string `json:"billing_preference"`
  17. }
  18. // ---- User APIs ----
  19. func GetSubscriptionPlans(c *gin.Context) {
  20. var plans []model.SubscriptionPlan
  21. if err := model.DB.Where("enabled = ?", true).Order("sort_order desc, id desc").Find(&plans).Error; err != nil {
  22. common.ApiError(c, err)
  23. return
  24. }
  25. result := make([]SubscriptionPlanDTO, 0, len(plans))
  26. for _, p := range plans {
  27. result = append(result, SubscriptionPlanDTO{
  28. Plan: p,
  29. })
  30. }
  31. common.ApiSuccess(c, result)
  32. }
  33. func GetSubscriptionSelf(c *gin.Context) {
  34. userId := c.GetInt("id")
  35. settingMap, _ := model.GetUserSetting(userId, false)
  36. pref := common.NormalizeBillingPreference(settingMap.BillingPreference)
  37. // Get all subscriptions (including expired)
  38. allSubscriptions, err := model.GetAllUserSubscriptions(userId)
  39. if err != nil {
  40. allSubscriptions = []model.SubscriptionSummary{}
  41. }
  42. // Get active subscriptions for backward compatibility
  43. activeSubscriptions, err := model.GetAllActiveUserSubscriptions(userId)
  44. if err != nil {
  45. activeSubscriptions = []model.SubscriptionSummary{}
  46. }
  47. common.ApiSuccess(c, gin.H{
  48. "billing_preference": pref,
  49. "subscriptions": activeSubscriptions, // all active subscriptions
  50. "all_subscriptions": allSubscriptions, // all subscriptions including expired
  51. })
  52. }
  53. func UpdateSubscriptionPreference(c *gin.Context) {
  54. userId := c.GetInt("id")
  55. var req BillingPreferenceRequest
  56. if err := c.ShouldBindJSON(&req); err != nil {
  57. common.ApiErrorMsg(c, "参数错误")
  58. return
  59. }
  60. pref := common.NormalizeBillingPreference(req.BillingPreference)
  61. user, err := model.GetUserById(userId, true)
  62. if err != nil {
  63. common.ApiError(c, err)
  64. return
  65. }
  66. current := user.GetSetting()
  67. current.BillingPreference = pref
  68. user.SetSetting(current)
  69. if err := user.Update(false); err != nil {
  70. common.ApiError(c, err)
  71. return
  72. }
  73. common.ApiSuccess(c, gin.H{"billing_preference": pref})
  74. }
  75. // ---- Admin APIs ----
  76. func AdminListSubscriptionPlans(c *gin.Context) {
  77. var plans []model.SubscriptionPlan
  78. if err := model.DB.Order("sort_order desc, id desc").Find(&plans).Error; err != nil {
  79. common.ApiError(c, err)
  80. return
  81. }
  82. result := make([]SubscriptionPlanDTO, 0, len(plans))
  83. for _, p := range plans {
  84. result = append(result, SubscriptionPlanDTO{
  85. Plan: p,
  86. })
  87. }
  88. common.ApiSuccess(c, result)
  89. }
  90. type AdminUpsertSubscriptionPlanRequest struct {
  91. Plan model.SubscriptionPlan `json:"plan"`
  92. }
  93. func AdminCreateSubscriptionPlan(c *gin.Context) {
  94. var req AdminUpsertSubscriptionPlanRequest
  95. if err := c.ShouldBindJSON(&req); err != nil {
  96. common.ApiErrorMsg(c, "参数错误")
  97. return
  98. }
  99. req.Plan.Id = 0
  100. if strings.TrimSpace(req.Plan.Title) == "" {
  101. common.ApiErrorMsg(c, "套餐标题不能为空")
  102. return
  103. }
  104. if req.Plan.PriceAmount < 0 {
  105. common.ApiErrorMsg(c, "价格不能为负数")
  106. return
  107. }
  108. if req.Plan.PriceAmount > 9999 {
  109. common.ApiErrorMsg(c, "价格不能超过9999")
  110. return
  111. }
  112. if req.Plan.Currency == "" {
  113. req.Plan.Currency = "USD"
  114. }
  115. req.Plan.Currency = "USD"
  116. if req.Plan.DurationUnit == "" {
  117. req.Plan.DurationUnit = model.SubscriptionDurationMonth
  118. }
  119. if req.Plan.DurationValue <= 0 && req.Plan.DurationUnit != model.SubscriptionDurationCustom {
  120. req.Plan.DurationValue = 1
  121. }
  122. if req.Plan.MaxPurchasePerUser < 0 {
  123. common.ApiErrorMsg(c, "购买上限不能为负数")
  124. return
  125. }
  126. if req.Plan.TotalAmount < 0 {
  127. common.ApiErrorMsg(c, "总额度不能为负数")
  128. return
  129. }
  130. req.Plan.UpgradeGroup = strings.TrimSpace(req.Plan.UpgradeGroup)
  131. if req.Plan.UpgradeGroup != "" {
  132. if _, ok := ratio_setting.GetGroupRatioCopy()[req.Plan.UpgradeGroup]; !ok {
  133. common.ApiErrorMsg(c, "升级分组不存在")
  134. return
  135. }
  136. }
  137. req.Plan.QuotaResetPeriod = model.NormalizeResetPeriod(req.Plan.QuotaResetPeriod)
  138. if req.Plan.QuotaResetPeriod == model.SubscriptionResetCustom && req.Plan.QuotaResetCustomSeconds <= 0 {
  139. common.ApiErrorMsg(c, "自定义重置周期需大于0秒")
  140. return
  141. }
  142. err := model.DB.Create(&req.Plan).Error
  143. if err != nil {
  144. common.ApiError(c, err)
  145. return
  146. }
  147. model.InvalidateSubscriptionPlanCache(req.Plan.Id)
  148. common.ApiSuccess(c, req.Plan)
  149. }
  150. func AdminUpdateSubscriptionPlan(c *gin.Context) {
  151. id, _ := strconv.Atoi(c.Param("id"))
  152. if id <= 0 {
  153. common.ApiErrorMsg(c, "无效的ID")
  154. return
  155. }
  156. var req AdminUpsertSubscriptionPlanRequest
  157. if err := c.ShouldBindJSON(&req); err != nil {
  158. common.ApiErrorMsg(c, "参数错误")
  159. return
  160. }
  161. if strings.TrimSpace(req.Plan.Title) == "" {
  162. common.ApiErrorMsg(c, "套餐标题不能为空")
  163. return
  164. }
  165. if req.Plan.PriceAmount < 0 {
  166. common.ApiErrorMsg(c, "价格不能为负数")
  167. return
  168. }
  169. if req.Plan.PriceAmount > 9999 {
  170. common.ApiErrorMsg(c, "价格不能超过9999")
  171. return
  172. }
  173. req.Plan.Id = id
  174. if req.Plan.Currency == "" {
  175. req.Plan.Currency = "USD"
  176. }
  177. req.Plan.Currency = "USD"
  178. if req.Plan.DurationUnit == "" {
  179. req.Plan.DurationUnit = model.SubscriptionDurationMonth
  180. }
  181. if req.Plan.DurationValue <= 0 && req.Plan.DurationUnit != model.SubscriptionDurationCustom {
  182. req.Plan.DurationValue = 1
  183. }
  184. if req.Plan.MaxPurchasePerUser < 0 {
  185. common.ApiErrorMsg(c, "购买上限不能为负数")
  186. return
  187. }
  188. if req.Plan.TotalAmount < 0 {
  189. common.ApiErrorMsg(c, "总额度不能为负数")
  190. return
  191. }
  192. req.Plan.UpgradeGroup = strings.TrimSpace(req.Plan.UpgradeGroup)
  193. if req.Plan.UpgradeGroup != "" {
  194. if _, ok := ratio_setting.GetGroupRatioCopy()[req.Plan.UpgradeGroup]; !ok {
  195. common.ApiErrorMsg(c, "升级分组不存在")
  196. return
  197. }
  198. }
  199. req.Plan.QuotaResetPeriod = model.NormalizeResetPeriod(req.Plan.QuotaResetPeriod)
  200. if req.Plan.QuotaResetPeriod == model.SubscriptionResetCustom && req.Plan.QuotaResetCustomSeconds <= 0 {
  201. common.ApiErrorMsg(c, "自定义重置周期需大于0秒")
  202. return
  203. }
  204. err := model.DB.Transaction(func(tx *gorm.DB) error {
  205. // update plan (allow zero values updates with map)
  206. updateMap := map[string]interface{}{
  207. "title": req.Plan.Title,
  208. "subtitle": req.Plan.Subtitle,
  209. "price_amount": req.Plan.PriceAmount,
  210. "currency": req.Plan.Currency,
  211. "duration_unit": req.Plan.DurationUnit,
  212. "duration_value": req.Plan.DurationValue,
  213. "custom_seconds": req.Plan.CustomSeconds,
  214. "enabled": req.Plan.Enabled,
  215. "sort_order": req.Plan.SortOrder,
  216. "stripe_price_id": req.Plan.StripePriceId,
  217. "creem_product_id": req.Plan.CreemProductId,
  218. "max_purchase_per_user": req.Plan.MaxPurchasePerUser,
  219. "total_amount": req.Plan.TotalAmount,
  220. "upgrade_group": req.Plan.UpgradeGroup,
  221. "quota_reset_period": req.Plan.QuotaResetPeriod,
  222. "quota_reset_custom_seconds": req.Plan.QuotaResetCustomSeconds,
  223. "updated_at": common.GetTimestamp(),
  224. }
  225. if err := tx.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Updates(updateMap).Error; err != nil {
  226. return err
  227. }
  228. return nil
  229. })
  230. if err != nil {
  231. common.ApiError(c, err)
  232. return
  233. }
  234. model.InvalidateSubscriptionPlanCache(id)
  235. common.ApiSuccess(c, nil)
  236. }
  237. type AdminUpdateSubscriptionPlanStatusRequest struct {
  238. Enabled *bool `json:"enabled"`
  239. }
  240. func AdminUpdateSubscriptionPlanStatus(c *gin.Context) {
  241. id, _ := strconv.Atoi(c.Param("id"))
  242. if id <= 0 {
  243. common.ApiErrorMsg(c, "无效的ID")
  244. return
  245. }
  246. var req AdminUpdateSubscriptionPlanStatusRequest
  247. if err := c.ShouldBindJSON(&req); err != nil || req.Enabled == nil {
  248. common.ApiErrorMsg(c, "参数错误")
  249. return
  250. }
  251. if err := model.DB.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Update("enabled", *req.Enabled).Error; err != nil {
  252. common.ApiError(c, err)
  253. return
  254. }
  255. model.InvalidateSubscriptionPlanCache(id)
  256. common.ApiSuccess(c, nil)
  257. }
  258. type AdminBindSubscriptionRequest struct {
  259. UserId int `json:"user_id"`
  260. PlanId int `json:"plan_id"`
  261. }
  262. func AdminBindSubscription(c *gin.Context) {
  263. var req AdminBindSubscriptionRequest
  264. if err := c.ShouldBindJSON(&req); err != nil || req.UserId <= 0 || req.PlanId <= 0 {
  265. common.ApiErrorMsg(c, "参数错误")
  266. return
  267. }
  268. msg, err := model.AdminBindSubscription(req.UserId, req.PlanId, "")
  269. if err != nil {
  270. common.ApiError(c, err)
  271. return
  272. }
  273. if msg != "" {
  274. common.ApiSuccess(c, gin.H{"message": msg})
  275. return
  276. }
  277. common.ApiSuccess(c, nil)
  278. }
  279. // ---- Admin: user subscription management ----
  280. func AdminListUserSubscriptions(c *gin.Context) {
  281. userId, _ := strconv.Atoi(c.Param("id"))
  282. if userId <= 0 {
  283. common.ApiErrorMsg(c, "无效的用户ID")
  284. return
  285. }
  286. subs, err := model.GetAllUserSubscriptions(userId)
  287. if err != nil {
  288. common.ApiError(c, err)
  289. return
  290. }
  291. common.ApiSuccess(c, subs)
  292. }
  293. type AdminCreateUserSubscriptionRequest struct {
  294. PlanId int `json:"plan_id"`
  295. }
  296. // AdminCreateUserSubscription creates a new user subscription from a plan (no payment).
  297. func AdminCreateUserSubscription(c *gin.Context) {
  298. userId, _ := strconv.Atoi(c.Param("id"))
  299. if userId <= 0 {
  300. common.ApiErrorMsg(c, "无效的用户ID")
  301. return
  302. }
  303. var req AdminCreateUserSubscriptionRequest
  304. if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
  305. common.ApiErrorMsg(c, "参数错误")
  306. return
  307. }
  308. msg, err := model.AdminBindSubscription(userId, req.PlanId, "")
  309. if err != nil {
  310. common.ApiError(c, err)
  311. return
  312. }
  313. if msg != "" {
  314. common.ApiSuccess(c, gin.H{"message": msg})
  315. return
  316. }
  317. common.ApiSuccess(c, nil)
  318. }
  319. // AdminInvalidateUserSubscription cancels a user subscription immediately.
  320. func AdminInvalidateUserSubscription(c *gin.Context) {
  321. subId, _ := strconv.Atoi(c.Param("id"))
  322. if subId <= 0 {
  323. common.ApiErrorMsg(c, "无效的订阅ID")
  324. return
  325. }
  326. msg, err := model.AdminInvalidateUserSubscription(subId)
  327. if err != nil {
  328. common.ApiError(c, err)
  329. return
  330. }
  331. if msg != "" {
  332. common.ApiSuccess(c, gin.H{"message": msg})
  333. return
  334. }
  335. common.ApiSuccess(c, nil)
  336. }
  337. // AdminDeleteUserSubscription hard-deletes a user subscription.
  338. func AdminDeleteUserSubscription(c *gin.Context) {
  339. subId, _ := strconv.Atoi(c.Param("id"))
  340. if subId <= 0 {
  341. common.ApiErrorMsg(c, "无效的订阅ID")
  342. return
  343. }
  344. msg, err := model.AdminDeleteUserSubscription(subId)
  345. if err != nil {
  346. common.ApiError(c, err)
  347. return
  348. }
  349. if msg != "" {
  350. common.ApiSuccess(c, gin.H{"message": msg})
  351. return
  352. }
  353. common.ApiSuccess(c, nil)
  354. }