wallet.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. package daytask
  2. import (
  3. "app/commons/model/entity"
  4. "app/commons/services"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/gin-gonic/gin"
  8. "time"
  9. )
  10. // WalletInfo 钱包信息
  11. func (s *Server) WalletInfo(c *gin.Context) {
  12. ctx := s.FromContext(c)
  13. db := s.DB()
  14. userId := ctx.UserId()
  15. user := &entity.DtUser{}
  16. if err := db.Where("id = ?", userId).First(user).Error; err != nil {
  17. ctx.Fail("user_not_found")
  18. return
  19. }
  20. ctx.OK(gin.H{
  21. "balance": user.Balance,
  22. "frozenBalance": user.FrozenBalance,
  23. "totalRecharge": user.TotalRecharge,
  24. "totalWithdraw": user.TotalWithdraw,
  25. "totalTaskIncome": user.TotalTaskIncome,
  26. "totalInviteIncome": user.TotalInviteIncome,
  27. })
  28. }
  29. // BalanceLog 余额流水
  30. func (s *Server) BalanceLog(c *gin.Context) {
  31. ctx := s.FromContext(c)
  32. db := s.DB()
  33. userId := ctx.UserId()
  34. logType := ctx.QueryInt64("type", 0) // 0表示全部
  35. paging := &services.Pagination{
  36. Current: ctx.QueryInt64("current", 1),
  37. Size: ctx.QueryInt64("size", 20),
  38. }
  39. query := db.Model(&entity.DtBalanceLog{}).Where("user_id = ?", userId)
  40. if logType > 0 {
  41. query = query.Where("type = ?", logType)
  42. }
  43. query.Count(&paging.Total)
  44. paging.Computer()
  45. logs := make([]*entity.DtBalanceLog, 0)
  46. query.Order("created_at DESC").
  47. Offset(int(paging.Start)).
  48. Limit(int(paging.Size)).
  49. Find(&logs)
  50. ctx.OK(gin.H{
  51. "list": logs,
  52. "paging": paging,
  53. })
  54. }
  55. // WalletWithdraw 申请提现
  56. func (s *Server) WalletWithdraw(c *gin.Context) {
  57. ctx := s.FromContext(c)
  58. db := s.DB()
  59. userId := ctx.UserId()
  60. type WithdrawRequest struct {
  61. Amount float64 `json:"amount" binding:"required"`
  62. PaymentId int64 `json:"paymentId" binding:"required"`
  63. }
  64. var req WithdrawRequest
  65. if err := c.ShouldBindJSON(&req); err != nil {
  66. ctx.Fail("invalid_params")
  67. return
  68. }
  69. // 获取用户
  70. user := &entity.DtUser{}
  71. if err := db.Where("id = ?", userId).First(user).Error; err != nil {
  72. ctx.Fail("user_not_found")
  73. return
  74. }
  75. // 检查余额
  76. if user.Balance < req.Amount {
  77. ctx.Fail("balance_not_enough")
  78. return
  79. }
  80. // 获取最低提现金额配置
  81. minWithdraw := 100.0 // 默认100
  82. config := &entity.DtConfig{}
  83. if err := db.Where("`key` = ?", "min_withdraw").First(config).Error; err == nil {
  84. fmt.Sscanf(config.Value, "%f", &minWithdraw)
  85. }
  86. if req.Amount < minWithdraw {
  87. ctx.Fail("amount_too_small")
  88. return
  89. }
  90. // 检查金额是否是10的倍数
  91. if int(req.Amount)%10 != 0 {
  92. ctx.Fail("amount_must_be_multiple_of_10")
  93. return
  94. }
  95. // 获取收款账户
  96. payment := &entity.DtUserPayment{}
  97. if err := db.Where("id = ? AND user_id = ?", req.PaymentId, userId).First(payment).Error; err != nil {
  98. ctx.Fail("payment_not_found")
  99. return
  100. }
  101. // 获取手续费率
  102. feeRate := 0.02 // 默认2%
  103. feeConfig := &entity.DtConfig{}
  104. if err := db.Where("`key` = ?", "withdraw_fee").First(feeConfig).Error; err == nil {
  105. fmt.Sscanf(feeConfig.Value, "%f", &feeRate)
  106. }
  107. fee := req.Amount * feeRate
  108. actualAmount := req.Amount - fee
  109. // 生成订单号
  110. orderNo := fmt.Sprintf("W%d%d", time.Now().UnixNano(), userId)
  111. // 序列化收款账户信息
  112. paymentInfo, _ := json.Marshal(payment)
  113. // 开始事务
  114. tx := db.Begin()
  115. // 扣除余额(原子操作 + 检查影响行数)
  116. result := tx.Model(&entity.DtUser{}).
  117. Where("id = ? AND balance >= ?", userId, req.Amount).
  118. Update("balance", tx.Raw("balance - ?", req.Amount))
  119. if result.Error != nil {
  120. tx.Rollback()
  121. ctx.Fail("deduct_balance_failed")
  122. return
  123. }
  124. if result.RowsAffected == 0 {
  125. tx.Rollback()
  126. ctx.Fail("balance_not_enough")
  127. return
  128. }
  129. // 重新查询扣款后的余额
  130. var updatedUser entity.DtUser
  131. tx.Where("id = ?", userId).First(&updatedUser)
  132. // 创建提现订单
  133. order := &entity.DtWithdrawOrder{
  134. OrderNo: orderNo,
  135. UserId: userId,
  136. Amount: req.Amount,
  137. Fee: fee,
  138. ActualAmount: actualAmount,
  139. PaymentType: payment.Type,
  140. PaymentId: payment.Id,
  141. PaymentInfo: string(paymentInfo),
  142. Status: entity.WithdrawStatusPending,
  143. }
  144. if err := tx.Create(order).Error; err != nil {
  145. tx.Rollback()
  146. ctx.Fail("create_order_failed")
  147. return
  148. }
  149. // 记录余额变动
  150. balanceLog := &entity.DtBalanceLog{
  151. UserId: userId,
  152. Type: entity.BalanceLogTypeWithdraw,
  153. Amount: -req.Amount,
  154. BeforeBalance: updatedUser.Balance + req.Amount,
  155. AfterBalance: updatedUser.Balance,
  156. RelatedId: order.Id,
  157. Remark: fmt.Sprintf("%s %s", ctx.I18n("withdraw_apply"), orderNo),
  158. }
  159. tx.Create(balanceLog)
  160. tx.Commit()
  161. ctx.OK(order)
  162. }
  163. // WithdrawConfig 提现配置
  164. func (s *Server) WithdrawConfig(c *gin.Context) {
  165. ctx := s.FromContext(c)
  166. db := s.DB()
  167. // 获取最低提现金额
  168. minWithdraw := 100.0
  169. minConfig := &entity.DtConfig{}
  170. if err := db.Where("`key` = ?", "min_withdraw").First(minConfig).Error; err == nil {
  171. fmt.Sscanf(minConfig.Value, "%f", &minWithdraw)
  172. }
  173. // 获取手续费率
  174. feeRate := 0.02
  175. feeConfig := &entity.DtConfig{}
  176. if err := db.Where("`key` = ?", "withdraw_fee").First(feeConfig).Error; err == nil {
  177. fmt.Sscanf(feeConfig.Value, "%f", &feeRate)
  178. }
  179. // 获取最大提现金额
  180. maxWithdraw := 50000.0
  181. maxConfig := &entity.DtConfig{}
  182. if err := db.Where("`key` = ?", "max_withdraw").First(maxConfig).Error; err == nil {
  183. fmt.Sscanf(maxConfig.Value, "%f", &maxWithdraw)
  184. }
  185. ctx.OK(gin.H{
  186. "minAmount": minWithdraw,
  187. "maxAmount": maxWithdraw,
  188. "feeRate": feeRate,
  189. "multiple": 10, // 提现金额必须是10的倍数
  190. })
  191. }
  192. // WithdrawLog 提现记录
  193. func (s *Server) WithdrawLog(c *gin.Context) {
  194. ctx := s.FromContext(c)
  195. db := s.DB()
  196. userId := ctx.UserId()
  197. status := ctx.QueryInt64("status", -99) // -99表示全部
  198. paging := &services.Pagination{
  199. Current: ctx.QueryInt64("current", 1),
  200. Size: ctx.QueryInt64("size", 20),
  201. }
  202. query := db.Model(&entity.DtWithdrawOrder{}).Where("user_id = ?", userId)
  203. if status != -99 {
  204. query = query.Where("status = ?", status)
  205. }
  206. query.Count(&paging.Total)
  207. paging.Computer()
  208. orders := make([]*entity.DtWithdrawOrder, 0)
  209. query.Order("created_at DESC").
  210. Offset(int(paging.Start)).
  211. Limit(int(paging.Size)).
  212. Find(&orders)
  213. ctx.OK(gin.H{
  214. "list": orders,
  215. "paging": paging,
  216. })
  217. }