subscription_payment_creem.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package controller
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "time"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/QuantumNous/new-api/logger"
  10. "github.com/QuantumNous/new-api/model"
  11. "github.com/QuantumNous/new-api/setting"
  12. "github.com/QuantumNous/new-api/setting/operation_setting"
  13. "github.com/gin-gonic/gin"
  14. "github.com/thanhpk/randstr"
  15. )
  16. type SubscriptionCreemPayRequest struct {
  17. PlanId int `json:"plan_id"`
  18. }
  19. func SubscriptionRequestCreemPay(c *gin.Context) {
  20. var req SubscriptionCreemPayRequest
  21. // Keep body for debugging consistency (like RequestCreemPay)
  22. bodyBytes, err := io.ReadAll(c.Request.Body)
  23. if err != nil {
  24. logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 订阅支付请求读取失败 error=%q", err.Error()))
  25. c.JSON(http.StatusOK, gin.H{"message": "error", "data": "read query error"})
  26. return
  27. }
  28. c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
  29. if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
  30. c.JSON(http.StatusOK, gin.H{"message": "error", "data": "参数错误"})
  31. return
  32. }
  33. plan, err := model.GetSubscriptionPlanById(req.PlanId)
  34. if err != nil {
  35. common.ApiError(c, err)
  36. return
  37. }
  38. if !plan.Enabled {
  39. common.ApiErrorMsg(c, "套餐未启用")
  40. return
  41. }
  42. if plan.CreemProductId == "" {
  43. common.ApiErrorMsg(c, "该套餐未配置 CreemProductId")
  44. return
  45. }
  46. if setting.CreemWebhookSecret == "" && !setting.CreemTestMode {
  47. common.ApiErrorMsg(c, "Creem Webhook 未配置")
  48. return
  49. }
  50. userId := c.GetInt("id")
  51. user, err := model.GetUserById(userId, false)
  52. if err != nil {
  53. common.ApiError(c, err)
  54. return
  55. }
  56. if user == nil {
  57. common.ApiErrorMsg(c, "用户不存在")
  58. return
  59. }
  60. if plan.MaxPurchasePerUser > 0 {
  61. count, err := model.CountUserSubscriptionsByPlan(userId, plan.Id)
  62. if err != nil {
  63. common.ApiError(c, err)
  64. return
  65. }
  66. if count >= int64(plan.MaxPurchasePerUser) {
  67. common.ApiErrorMsg(c, "已达到该套餐购买上限")
  68. return
  69. }
  70. }
  71. reference := "sub-creem-ref-" + randstr.String(6)
  72. referenceId := "sub_ref_" + common.Sha1([]byte(reference+time.Now().String()+user.Username))
  73. // create pending order first
  74. order := &model.SubscriptionOrder{
  75. UserId: userId,
  76. PlanId: plan.Id,
  77. Money: plan.PriceAmount,
  78. TradeNo: referenceId,
  79. PaymentMethod: model.PaymentMethodCreem,
  80. PaymentProvider: model.PaymentProviderCreem,
  81. CreateTime: time.Now().Unix(),
  82. Status: common.TopUpStatusPending,
  83. }
  84. if err := order.Insert(); err != nil {
  85. c.JSON(http.StatusOK, gin.H{"message": "error", "data": "创建订单失败"})
  86. return
  87. }
  88. // Reuse Creem checkout generator by building a lightweight product reference.
  89. currency := "USD"
  90. switch operation_setting.GetGeneralSetting().QuotaDisplayType {
  91. case operation_setting.QuotaDisplayTypeCNY:
  92. currency = "CNY"
  93. case operation_setting.QuotaDisplayTypeUSD:
  94. currency = "USD"
  95. default:
  96. currency = "USD"
  97. }
  98. product := &CreemProduct{
  99. ProductId: plan.CreemProductId,
  100. Name: plan.Title,
  101. Price: plan.PriceAmount,
  102. Currency: currency,
  103. Quota: 0,
  104. }
  105. checkoutUrl, err := genCreemLink(c.Request.Context(), referenceId, product, user.Email, user.Username)
  106. if err != nil {
  107. logger.LogError(c.Request.Context(), fmt.Sprintf("Creem 订阅支付链接创建失败 trade_no=%s product_id=%s error=%q", referenceId, product.ProductId, err.Error()))
  108. c.JSON(http.StatusOK, gin.H{"message": "error", "data": "拉起支付失败"})
  109. return
  110. }
  111. c.JSON(http.StatusOK, gin.H{
  112. "message": "success",
  113. "data": gin.H{
  114. "checkout_url": checkoutUrl,
  115. "order_id": referenceId,
  116. },
  117. })
  118. }