com_handler_asset_withdraw.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package services
  2. import (
  3. "app/commons/constant"
  4. "app/commons/core"
  5. "app/commons/core/exchange"
  6. "app/commons/core/redisclient"
  7. "app/commons/model/entity"
  8. "app/commons/utils"
  9. "errors"
  10. "fmt"
  11. "github.com/shopspring/decimal"
  12. )
  13. // 提现检查
  14. func (s *CommonService) WithdrawCheck(userId int64, symbol string, amount decimal.Decimal) (*entity.User, error) {
  15. if amount.LessThanOrEqual(decimal.Zero) {
  16. return nil, fmt.Errorf(constant.ErrorResponseTransferAmountInvalid)
  17. }
  18. conf, err := s.AssetConfigFromCache()
  19. if err != nil {
  20. return nil, err
  21. }
  22. if !conf.WithdrawStatus {
  23. return nil, fmt.Errorf(constant.ErrorResponseTransferWithdrawSystemTending)
  24. }
  25. if conf.MaxWithdrawAmount.Equal(decimal.Zero) {
  26. return nil, fmt.Errorf(constant.ErrorResponseTransferWithdrawSystemTending)
  27. }
  28. if conf.MinWithdrawAmount.GreaterThan(amount) {
  29. return nil, fmt.Errorf(constant.ErrorResponseUnderMinAmount)
  30. }
  31. user, err := s.GetUserByUserId(userId)
  32. if err != nil {
  33. return nil, err
  34. }
  35. if !user.LockWithdraw {
  36. return nil, fmt.Errorf(constant.ErrorResponseExceedAmountLimit)
  37. }
  38. asset, err := s.GetAssetBySymbol(userId, symbol)
  39. if err != nil {
  40. return nil, fmt.Errorf(constant.ErrorResponseExceedAmountLimit)
  41. }
  42. if asset.Balance.LessThan(amount) {
  43. return nil, fmt.Errorf(constant.ErrorResponseBalanceNotEnough)
  44. }
  45. return user, nil
  46. }
  47. // 提现申请
  48. func (s *CommonService) WithdrawApply(user *entity.User, symbol string, amount decimal.Decimal) error {
  49. //检查当前是否有提现中订单 如果有则不允许提现
  50. // 避免出现同时多订单提现 导致被交易所封控
  51. row, err := s.FirstAssetRwRecord(s.DB().
  52. Where("user_id", user.Id).
  53. Where("direction", constant.RwDirectionWithdraw).
  54. Where("status in (?,?)", constant.RwStateWaiting, constant.RwStatePass))
  55. if err == nil && row.Id > 0 {
  56. return fmt.Errorf("the withdrawal is currently being processed")
  57. }
  58. userAsset, err := s.CheckUserAssetByUserIdSymbol(s.DB(), user.Id, symbol)
  59. if err != nil {
  60. return fmt.Errorf("asset not found")
  61. }
  62. if userAsset.Balance.LessThan(amount) {
  63. return fmt.Errorf("balance inadequate")
  64. }
  65. // 获取分布式锁 存在用户购买商品 发放奖励等情况 可能会影响资产变化
  66. lockKey := fmt.Sprintf("%s:%d", constant.UserAssetLocker, user.Id)
  67. lock, err := redisclient.Lock(lockKey)
  68. if err != nil {
  69. return fmt.Errorf("failed to acquire lock: %v", err)
  70. }
  71. core.Log.Infof("用户ID:%s 资产锁获取", lockKey)
  72. defer func() {
  73. err = redisclient.UnlockSafe(lock)
  74. if err != nil {
  75. core.Log.Error(err)
  76. }
  77. core.Log.Infof("用户ID:%s 资产锁释放", lockKey)
  78. }()
  79. conf, err := s.AssetConfigFromCache()
  80. if err != nil {
  81. core.Log.Error(err)
  82. return err
  83. }
  84. feeRatio := decimal.NewFromFloat(0) // 默认0%手续费
  85. if conf.WithdrawRatio.GreaterThan(decimal.Zero) && conf.WithdrawRatio.LessThan(decimal.NewFromFloat(1)) {
  86. feeRatio = conf.WithdrawRatio
  87. }
  88. feeBaseAmount := conf.WithdrawBaseAmount // 单笔提现基础手续费
  89. fee := amount.Mul(feeRatio).Add(feeBaseAmount) // 手续费 = 手续费比例 + 单次手续费
  90. realAmount := amount.Sub(fee) // 实际转账
  91. isPass := conf.WithdrawNoauditLimit.LessThanOrEqual(amount)
  92. if realAmount.LessThan(decimal.Zero) {
  93. return fmt.Errorf(constant.ErrorResponseBalanceNotEnough)
  94. }
  95. // 构建转账订单
  96. withdrawOrder := s.buildRwRecord(
  97. symbol,
  98. user.Id,
  99. user.OpenId,
  100. utils.UuidPrefix("W"),
  101. constant.RwDirectionWithdraw,
  102. amount,
  103. feeRatio,
  104. feeBaseAmount,
  105. fee,
  106. realAmount,
  107. )
  108. dbTx := s.DB().Begin()
  109. if isPass {
  110. // 修改订单状态
  111. withdrawOrder.Status = constant.RwStatePass // 审核完成 转账中
  112. }
  113. withdrawOrder.WithdrawState = constant.WithdrawStatePending // 待转账
  114. // 修改订单状态
  115. err = dbTx.Create(&withdrawOrder).Error
  116. if err != nil {
  117. dbTx.Rollback()
  118. return err
  119. }
  120. // 创建流水 冻结可用余额
  121. err = s.GenBillAndActionAsset(dbTx, userAsset.UserId, userAsset.Symbol, decimal.Zero.Sub(amount), amount, constant.BsById(constant.BsAssetWithdrawApply))
  122. if err != nil {
  123. dbTx.Rollback()
  124. return err
  125. }
  126. dbTx.Commit()
  127. // 发送转账处理信号
  128. go s.handleAllWithdraw()
  129. return nil
  130. }
  131. // 处理提现
  132. var withdrawIsRun = false
  133. func (s *CommonService) handleAllWithdraw() {
  134. // 使用内部锁
  135. if withdrawIsRun {
  136. return
  137. }
  138. withdrawIsRun = true
  139. defer func() {
  140. withdrawIsRun = false
  141. }()
  142. // 处理审核通过转账
  143. if err := s.handWithdrawPassOrder(); err != nil {
  144. core.Log.Errorf("处理审核通过转账 error:%s", err.Error())
  145. }
  146. // 处理驳回与系统失败转账
  147. if err := s.handWithdrawFailOrder(); err != nil {
  148. core.Log.Errorf("处理驳回与系统失败转账 error:%s", err.Error())
  149. }
  150. }
  151. // 处理提现订单 -- 状态为审核通过的产品
  152. func (s *CommonService) handWithdrawPassOrder() error {
  153. // 收到信号即处理全部订单
  154. items, err := s.BatchTransferRecord(s.DB().
  155. Where("status", constant.RwStatePass).
  156. Where("direction", constant.RwDirectionWithdraw).
  157. Where("transfer_state", constant.WithdrawStatePending))
  158. if err != nil {
  159. return err
  160. }
  161. for _, item := range items {
  162. item.WithdrawState = constant.WithdrawStateFinish
  163. _, err = s.withdrawToEx(item)
  164. if err != nil {
  165. // 资金退回处理
  166. item.Describe = fmt.Sprintf("%s\n%s", item.Describe, err.Error())
  167. core.Log.Errorf("系统 to 交易所 转账失败:%s", err.Error())
  168. item.Status = constant.RwStateSystemFail
  169. }
  170. if err = s.DB().Model(&entity.AssetRwRecord{}).Where("id", item.Id).Updates(
  171. map[string]interface{}{
  172. "status": item.Status,
  173. "describe": item.Describe,
  174. }).Error; err != nil {
  175. core.Log.Errorf("转出订单处理失败:%s 存储失败", err.Error())
  176. }
  177. }
  178. return nil
  179. }
  180. // 向交易所发起转账申请
  181. func (s *CommonService) withdrawToEx(req *entity.AssetRwRecord) (int, error) {
  182. // 这里仅做发起交易
  183. resp, err := exchange.TransferOut(req.OpenId, &exchange.TransferOutReq{
  184. Currency: req.Symbol,
  185. Amount: req.RealAmount.String(),
  186. OrderId: req.OrderId,
  187. })
  188. if err != nil {
  189. return 0, err
  190. }
  191. if resp.Code == 10000 {
  192. return resp.Code, errors.New(resp.Msg)
  193. }
  194. return resp.Code, nil
  195. }
  196. // 驳回订单处理
  197. func (s *CommonService) handWithdrawFailOrder() error {
  198. // 收到信号即处理全部订单
  199. itemsReject, err := s.BatchTransferRecord(s.DB().
  200. Where("status in (?,?)", constant.RwStateReject, constant.RwStateSystemFail).
  201. Where("direction", constant.RwDirectionWithdraw))
  202. if err != nil {
  203. return err
  204. }
  205. for _, item := range itemsReject {
  206. err = s.returnWithdraw(item)
  207. if err != nil {
  208. core.Log.Errorf("转账失败订单 资金退回错误:%s", err.Error())
  209. }
  210. }
  211. return nil
  212. }
  213. // 解冻资产 提现退回
  214. func (s *CommonService) returnWithdraw(item *entity.AssetRwRecord) error {
  215. var err error
  216. dbTx := s.DB().Begin()
  217. userAsset, err := s.CheckUserAssetByUserIdSymbol(dbTx, item.UserId, item.Symbol)
  218. if err != nil {
  219. err = fmt.Errorf("asset error")
  220. dbTx.Rollback()
  221. return err
  222. }
  223. if userAsset.Frozen.LessThan(item.Amount) {
  224. err = fmt.Errorf("OrderId:%s资金错误:冻结金额:%s小于提现金额:%s 转账 ", item.OrderId, userAsset.Frozen, item.Amount)
  225. dbTx.Rollback()
  226. return err
  227. }
  228. if err = dbTx.Model(&entity.AssetRwRecord{}).Where("id", item.Id).
  229. Update("Status", constant.RwStateReturnSuccess).Error; err != nil {
  230. core.Log.Errorf("转账失败订单 资金退回错误:%s", err.Error())
  231. dbTx.Rollback()
  232. return err
  233. }
  234. // 修改用户余额 可用->冻结
  235. // 创建流水 冻结可用余额
  236. err = s.GenBillAndActionAsset(dbTx,
  237. userAsset.UserId,
  238. userAsset.Symbol,
  239. decimal.Zero,
  240. decimal.Zero.Sub(item.Amount),
  241. constant.BsById(constant.BsAssetWithdrawApply))
  242. if err != nil {
  243. dbTx.Rollback()
  244. return err
  245. }
  246. dbTx.Commit()
  247. return nil
  248. }