subscription_payment_epay.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package controller
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/url"
  6. "strconv"
  7. "time"
  8. "github.com/Calcium-Ion/go-epay/epay"
  9. "github.com/QuantumNous/new-api/common"
  10. "github.com/QuantumNous/new-api/model"
  11. "github.com/QuantumNous/new-api/service"
  12. "github.com/QuantumNous/new-api/setting/operation_setting"
  13. "github.com/QuantumNous/new-api/setting/system_setting"
  14. "github.com/gin-gonic/gin"
  15. "github.com/samber/lo"
  16. )
  17. type SubscriptionEpayPayRequest struct {
  18. PlanId int `json:"plan_id"`
  19. PaymentMethod string `json:"payment_method"`
  20. }
  21. func SubscriptionRequestEpay(c *gin.Context) {
  22. var req SubscriptionEpayPayRequest
  23. if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
  24. common.ApiErrorMsg(c, "参数错误")
  25. return
  26. }
  27. plan, err := model.GetSubscriptionPlanById(req.PlanId)
  28. if err != nil {
  29. common.ApiError(c, err)
  30. return
  31. }
  32. if !plan.Enabled {
  33. common.ApiErrorMsg(c, "套餐未启用")
  34. return
  35. }
  36. if plan.PriceAmount < 0.01 {
  37. common.ApiErrorMsg(c, "套餐金额过低")
  38. return
  39. }
  40. if !operation_setting.ContainsPayMethod(req.PaymentMethod) {
  41. common.ApiErrorMsg(c, "支付方式不存在")
  42. return
  43. }
  44. userId := c.GetInt("id")
  45. if plan.MaxPurchasePerUser > 0 {
  46. count, err := model.CountUserSubscriptionsByPlan(userId, plan.Id)
  47. if err != nil {
  48. common.ApiError(c, err)
  49. return
  50. }
  51. if count >= int64(plan.MaxPurchasePerUser) {
  52. common.ApiErrorMsg(c, "已达到该套餐购买上限")
  53. return
  54. }
  55. }
  56. callBackAddress := service.GetCallbackAddress()
  57. returnUrl, err := url.Parse(callBackAddress + "/api/subscription/epay/return")
  58. if err != nil {
  59. common.ApiErrorMsg(c, "回调地址配置错误")
  60. return
  61. }
  62. notifyUrl, err := url.Parse(callBackAddress + "/api/subscription/epay/notify")
  63. if err != nil {
  64. common.ApiErrorMsg(c, "回调地址配置错误")
  65. return
  66. }
  67. tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix())
  68. tradeNo = fmt.Sprintf("SUBUSR%dNO%s", userId, tradeNo)
  69. client := GetEpayClient()
  70. if client == nil {
  71. common.ApiErrorMsg(c, "当前管理员未配置支付信息")
  72. return
  73. }
  74. order := &model.SubscriptionOrder{
  75. UserId: userId,
  76. PlanId: plan.Id,
  77. Money: plan.PriceAmount,
  78. TradeNo: tradeNo,
  79. PaymentMethod: req.PaymentMethod,
  80. CreateTime: time.Now().Unix(),
  81. Status: common.TopUpStatusPending,
  82. }
  83. if err := order.Insert(); err != nil {
  84. common.ApiErrorMsg(c, "创建订单失败")
  85. return
  86. }
  87. uri, params, err := client.Purchase(&epay.PurchaseArgs{
  88. Type: req.PaymentMethod,
  89. ServiceTradeNo: tradeNo,
  90. Name: fmt.Sprintf("SUB:%s", plan.Title),
  91. Money: strconv.FormatFloat(plan.PriceAmount, 'f', 2, 64),
  92. Device: epay.PC,
  93. NotifyUrl: notifyUrl,
  94. ReturnUrl: returnUrl,
  95. })
  96. if err != nil {
  97. _ = model.ExpireSubscriptionOrder(tradeNo)
  98. common.ApiErrorMsg(c, "拉起支付失败")
  99. return
  100. }
  101. c.JSON(http.StatusOK, gin.H{"message": "success", "data": params, "url": uri})
  102. }
  103. func SubscriptionEpayNotify(c *gin.Context) {
  104. var params map[string]string
  105. if c.Request.Method == "POST" {
  106. // POST 请求:从 POST body 解析参数
  107. if err := c.Request.ParseForm(); err != nil {
  108. _, _ = c.Writer.Write([]byte("fail"))
  109. return
  110. }
  111. params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
  112. r[t] = c.Request.PostForm.Get(t)
  113. return r
  114. }, map[string]string{})
  115. } else {
  116. // GET 请求:从 URL Query 解析参数
  117. params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
  118. r[t] = c.Request.URL.Query().Get(t)
  119. return r
  120. }, map[string]string{})
  121. }
  122. if len(params) == 0 {
  123. _, _ = c.Writer.Write([]byte("fail"))
  124. return
  125. }
  126. client := GetEpayClient()
  127. if client == nil {
  128. _, _ = c.Writer.Write([]byte("fail"))
  129. return
  130. }
  131. verifyInfo, err := client.Verify(params)
  132. if err != nil || !verifyInfo.VerifyStatus {
  133. _, _ = c.Writer.Write([]byte("fail"))
  134. return
  135. }
  136. if verifyInfo.TradeStatus != epay.StatusTradeSuccess {
  137. _, _ = c.Writer.Write([]byte("fail"))
  138. return
  139. }
  140. LockOrder(verifyInfo.ServiceTradeNo)
  141. defer UnlockOrder(verifyInfo.ServiceTradeNo)
  142. if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo)); err != nil {
  143. _, _ = c.Writer.Write([]byte("fail"))
  144. return
  145. }
  146. _, _ = c.Writer.Write([]byte("success"))
  147. }
  148. // SubscriptionEpayReturn handles browser return after payment.
  149. // It verifies the payload and completes the order, then redirects to console.
  150. func SubscriptionEpayReturn(c *gin.Context) {
  151. var params map[string]string
  152. if c.Request.Method == "POST" {
  153. // POST 请求:从 POST body 解析参数
  154. if err := c.Request.ParseForm(); err != nil {
  155. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
  156. return
  157. }
  158. params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string {
  159. r[t] = c.Request.PostForm.Get(t)
  160. return r
  161. }, map[string]string{})
  162. } else {
  163. // GET 请求:从 URL Query 解析参数
  164. params = lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
  165. r[t] = c.Request.URL.Query().Get(t)
  166. return r
  167. }, map[string]string{})
  168. }
  169. if len(params) == 0 {
  170. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
  171. return
  172. }
  173. client := GetEpayClient()
  174. if client == nil {
  175. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
  176. return
  177. }
  178. verifyInfo, err := client.Verify(params)
  179. if err != nil || !verifyInfo.VerifyStatus {
  180. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
  181. return
  182. }
  183. if verifyInfo.TradeStatus == epay.StatusTradeSuccess {
  184. LockOrder(verifyInfo.ServiceTradeNo)
  185. defer UnlockOrder(verifyInfo.ServiceTradeNo)
  186. if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo)); err != nil {
  187. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=fail")
  188. return
  189. }
  190. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=success")
  191. return
  192. }
  193. c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/subscription?pay=pending")
  194. }