topup.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package controller
  2. import (
  3. "fmt"
  4. "log"
  5. "net/url"
  6. "one-api/common"
  7. "one-api/logger"
  8. "one-api/model"
  9. "one-api/service"
  10. "one-api/setting"
  11. "strconv"
  12. "sync"
  13. "time"
  14. "github.com/Calcium-Ion/go-epay/epay"
  15. "github.com/gin-gonic/gin"
  16. "github.com/samber/lo"
  17. "github.com/shopspring/decimal"
  18. )
  19. type EpayRequest struct {
  20. Amount int64 `json:"amount"`
  21. PaymentMethod string `json:"payment_method"`
  22. TopUpCode string `json:"top_up_code"`
  23. }
  24. type AmountRequest struct {
  25. Amount int64 `json:"amount"`
  26. TopUpCode string `json:"top_up_code"`
  27. }
  28. func GetEpayClient() *epay.Client {
  29. if setting.PayAddress == "" || setting.EpayId == "" || setting.EpayKey == "" {
  30. return nil
  31. }
  32. withUrl, err := epay.NewClient(&epay.Config{
  33. PartnerID: setting.EpayId,
  34. Key: setting.EpayKey,
  35. }, setting.PayAddress)
  36. if err != nil {
  37. return nil
  38. }
  39. return withUrl
  40. }
  41. func getPayMoney(amount int64, group string) float64 {
  42. dAmount := decimal.NewFromInt(amount)
  43. if !common.DisplayInCurrencyEnabled {
  44. dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
  45. dAmount = dAmount.Div(dQuotaPerUnit)
  46. }
  47. topupGroupRatio := common.GetTopupGroupRatio(group)
  48. if topupGroupRatio == 0 {
  49. topupGroupRatio = 1
  50. }
  51. dTopupGroupRatio := decimal.NewFromFloat(topupGroupRatio)
  52. dPrice := decimal.NewFromFloat(setting.Price)
  53. payMoney := dAmount.Mul(dPrice).Mul(dTopupGroupRatio)
  54. return payMoney.InexactFloat64()
  55. }
  56. func getMinTopup() int64 {
  57. minTopup := setting.MinTopUp
  58. if !common.DisplayInCurrencyEnabled {
  59. dMinTopup := decimal.NewFromInt(int64(minTopup))
  60. dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
  61. minTopup = int(dMinTopup.Mul(dQuotaPerUnit).IntPart())
  62. }
  63. return int64(minTopup)
  64. }
  65. func RequestEpay(c *gin.Context) {
  66. var req EpayRequest
  67. err := c.ShouldBindJSON(&req)
  68. if err != nil {
  69. c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
  70. return
  71. }
  72. if req.Amount < getMinTopup() {
  73. c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getMinTopup())})
  74. return
  75. }
  76. id := c.GetInt("id")
  77. group, err := model.GetUserGroup(id, true)
  78. if err != nil {
  79. c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"})
  80. return
  81. }
  82. payMoney := getPayMoney(req.Amount, group)
  83. if payMoney < 0.01 {
  84. c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"})
  85. return
  86. }
  87. if !setting.ContainsPayMethod(req.PaymentMethod) {
  88. c.JSON(200, gin.H{"message": "error", "data": "支付方式不存在"})
  89. return
  90. }
  91. callBackAddress := service.GetCallbackAddress()
  92. returnUrl, _ := url.Parse(setting.ServerAddress + "/console/log")
  93. notifyUrl, _ := url.Parse(callBackAddress + "/api/user/epay/notify")
  94. tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix())
  95. tradeNo = fmt.Sprintf("USR%dNO%s", id, tradeNo)
  96. client := GetEpayClient()
  97. if client == nil {
  98. c.JSON(200, gin.H{"message": "error", "data": "当前管理员未配置支付信息"})
  99. return
  100. }
  101. uri, params, err := client.Purchase(&epay.PurchaseArgs{
  102. Type: req.PaymentMethod,
  103. ServiceTradeNo: tradeNo,
  104. Name: fmt.Sprintf("TUC%d", req.Amount),
  105. Money: strconv.FormatFloat(payMoney, 'f', 2, 64),
  106. Device: epay.PC,
  107. NotifyUrl: notifyUrl,
  108. ReturnUrl: returnUrl,
  109. })
  110. if err != nil {
  111. c.JSON(200, gin.H{"message": "error", "data": "拉起支付失败"})
  112. return
  113. }
  114. amount := req.Amount
  115. if !common.DisplayInCurrencyEnabled {
  116. dAmount := decimal.NewFromInt(int64(amount))
  117. dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
  118. amount = dAmount.Div(dQuotaPerUnit).IntPart()
  119. }
  120. topUp := &model.TopUp{
  121. UserId: id,
  122. Amount: amount,
  123. Money: payMoney,
  124. TradeNo: tradeNo,
  125. CreateTime: time.Now().Unix(),
  126. Status: "pending",
  127. }
  128. err = topUp.Insert()
  129. if err != nil {
  130. c.JSON(200, gin.H{"message": "error", "data": "创建订单失败"})
  131. return
  132. }
  133. c.JSON(200, gin.H{"message": "success", "data": params, "url": uri})
  134. }
  135. // tradeNo lock
  136. var orderLocks sync.Map
  137. var createLock sync.Mutex
  138. // LockOrder 尝试对给定订单号加锁
  139. func LockOrder(tradeNo string) {
  140. lock, ok := orderLocks.Load(tradeNo)
  141. if !ok {
  142. createLock.Lock()
  143. defer createLock.Unlock()
  144. lock, ok = orderLocks.Load(tradeNo)
  145. if !ok {
  146. lock = new(sync.Mutex)
  147. orderLocks.Store(tradeNo, lock)
  148. }
  149. }
  150. lock.(*sync.Mutex).Lock()
  151. }
  152. // UnlockOrder 释放给定订单号的锁
  153. func UnlockOrder(tradeNo string) {
  154. lock, ok := orderLocks.Load(tradeNo)
  155. if ok {
  156. lock.(*sync.Mutex).Unlock()
  157. }
  158. }
  159. func EpayNotify(c *gin.Context) {
  160. params := lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
  161. r[t] = c.Request.URL.Query().Get(t)
  162. return r
  163. }, map[string]string{})
  164. client := GetEpayClient()
  165. if client == nil {
  166. log.Println("易支付回调失败 未找到配置信息")
  167. _, err := c.Writer.Write([]byte("fail"))
  168. if err != nil {
  169. log.Println("易支付回调写入失败")
  170. return
  171. }
  172. }
  173. verifyInfo, err := client.Verify(params)
  174. if err == nil && verifyInfo.VerifyStatus {
  175. _, err := c.Writer.Write([]byte("success"))
  176. if err != nil {
  177. log.Println("易支付回调写入失败")
  178. }
  179. } else {
  180. _, err := c.Writer.Write([]byte("fail"))
  181. if err != nil {
  182. log.Println("易支付回调写入失败")
  183. }
  184. log.Println("易支付回调签名验证失败")
  185. return
  186. }
  187. if verifyInfo.TradeStatus == epay.StatusTradeSuccess {
  188. log.Println(verifyInfo)
  189. LockOrder(verifyInfo.ServiceTradeNo)
  190. defer UnlockOrder(verifyInfo.ServiceTradeNo)
  191. topUp := model.GetTopUpByTradeNo(verifyInfo.ServiceTradeNo)
  192. if topUp == nil {
  193. log.Printf("易支付回调未找到订单: %v", verifyInfo)
  194. return
  195. }
  196. if topUp.Status == "pending" {
  197. topUp.Status = "success"
  198. err := topUp.Update()
  199. if err != nil {
  200. log.Printf("易支付回调更新订单失败: %v", topUp)
  201. return
  202. }
  203. //user, _ := model.GetUserById(topUp.UserId, false)
  204. //user.Quota += topUp.Amount * 500000
  205. dAmount := decimal.NewFromInt(int64(topUp.Amount))
  206. dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
  207. quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart())
  208. err = model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true)
  209. if err != nil {
  210. log.Printf("易支付回调更新用户失败: %v", topUp)
  211. return
  212. }
  213. log.Printf("易支付回调更新用户成功 %v", topUp)
  214. model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", logger.LogQuota(quotaToAdd), topUp.Money))
  215. }
  216. } else {
  217. log.Printf("易支付异常回调: %v", verifyInfo)
  218. }
  219. }
  220. func RequestAmount(c *gin.Context) {
  221. var req AmountRequest
  222. err := c.ShouldBindJSON(&req)
  223. if err != nil {
  224. c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
  225. return
  226. }
  227. if req.Amount < getMinTopup() {
  228. c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getMinTopup())})
  229. return
  230. }
  231. id := c.GetInt("id")
  232. group, err := model.GetUserGroup(id, true)
  233. if err != nil {
  234. c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"})
  235. return
  236. }
  237. payMoney := getPayMoney(req.Amount, group)
  238. if payMoney <= 0.01 {
  239. c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"})
  240. return
  241. }
  242. c.JSON(200, gin.H{"message": "success", "data": strconv.FormatFloat(payMoney, 'f', 2, 64)})
  243. }