| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- package services
- import (
- "app/commons/constant"
- "app/commons/core"
- "app/commons/core/exchange"
- "app/commons/core/redisclient"
- "app/commons/model/entity"
- "app/commons/utils"
- "errors"
- "fmt"
- "github.com/shopspring/decimal"
- )
- // 提现检查
- func (s *CommonService) WithdrawCheck(userId int64, symbol string, amount decimal.Decimal) (*entity.User, error) {
- if amount.LessThanOrEqual(decimal.Zero) {
- return nil, fmt.Errorf(constant.ErrorResponseTransferAmountInvalid)
- }
- conf, err := s.AssetConfigFromCache()
- if err != nil {
- return nil, err
- }
- if !conf.WithdrawStatus {
- return nil, fmt.Errorf(constant.ErrorResponseTransferWithdrawSystemTending)
- }
- if conf.MaxWithdrawAmount.Equal(decimal.Zero) {
- return nil, fmt.Errorf(constant.ErrorResponseTransferWithdrawSystemTending)
- }
- if conf.MinWithdrawAmount.GreaterThan(amount) {
- return nil, fmt.Errorf(constant.ErrorResponseUnderMinAmount)
- }
- user, err := s.GetUserByUserId(userId)
- if err != nil {
- return nil, err
- }
- if !user.LockWithdraw {
- return nil, fmt.Errorf(constant.ErrorResponseExceedAmountLimit)
- }
- asset, err := s.GetAssetBySymbol(userId, symbol)
- if err != nil {
- return nil, fmt.Errorf(constant.ErrorResponseExceedAmountLimit)
- }
- if asset.Balance.LessThan(amount) {
- return nil, fmt.Errorf(constant.ErrorResponseBalanceNotEnough)
- }
- return user, nil
- }
- // 提现申请
- func (s *CommonService) WithdrawApply(user *entity.User, symbol string, amount decimal.Decimal) error {
- //检查当前是否有提现中订单 如果有则不允许提现
- // 避免出现同时多订单提现 导致被交易所封控
- row, err := s.FirstAssetRwRecord(s.DB().
- Where("user_id", user.Id).
- Where("direction", constant.RwDirectionWithdraw).
- Where("status in (?,?)", constant.RwStateWaiting, constant.RwStatePass))
- if err == nil && row.Id > 0 {
- return fmt.Errorf("the withdrawal is currently being processed")
- }
- userAsset, err := s.CheckUserAssetByUserIdSymbol(s.DB(), user.Id, symbol)
- if err != nil {
- return fmt.Errorf("asset not found")
- }
- if userAsset.Balance.LessThan(amount) {
- return fmt.Errorf("balance inadequate")
- }
- // 获取分布式锁 存在用户购买商品 发放奖励等情况 可能会影响资产变化
- lockKey := fmt.Sprintf("%s:%d", constant.UserAssetLocker, user.Id)
- lock, err := redisclient.Lock(lockKey)
- if err != nil {
- return fmt.Errorf("failed to acquire lock: %v", err)
- }
- core.Log.Infof("用户ID:%s 资产锁获取", lockKey)
- defer func() {
- err = redisclient.UnlockSafe(lock)
- if err != nil {
- core.Log.Error(err)
- }
- core.Log.Infof("用户ID:%s 资产锁释放", lockKey)
- }()
- conf, err := s.AssetConfigFromCache()
- if err != nil {
- core.Log.Error(err)
- return err
- }
- feeRatio := decimal.NewFromFloat(0) // 默认0%手续费
- if conf.WithdrawRatio.GreaterThan(decimal.Zero) && conf.WithdrawRatio.LessThan(decimal.NewFromFloat(1)) {
- feeRatio = conf.WithdrawRatio
- }
- feeBaseAmount := conf.WithdrawBaseAmount // 单笔提现基础手续费
- fee := amount.Mul(feeRatio).Add(feeBaseAmount) // 手续费 = 手续费比例 + 单次手续费
- realAmount := amount.Sub(fee) // 实际转账
- isPass := conf.WithdrawNoauditLimit.LessThanOrEqual(amount)
- if realAmount.LessThan(decimal.Zero) {
- return fmt.Errorf(constant.ErrorResponseBalanceNotEnough)
- }
- // 构建转账订单
- withdrawOrder := s.buildRwRecord(
- symbol,
- user.Id,
- user.OpenId,
- utils.UuidPrefix("W"),
- constant.RwDirectionWithdraw,
- amount,
- feeRatio,
- feeBaseAmount,
- fee,
- realAmount,
- )
- dbTx := s.DB().Begin()
- if isPass {
- // 修改订单状态
- withdrawOrder.Status = constant.RwStatePass // 审核完成 转账中
- }
- withdrawOrder.WithdrawState = constant.WithdrawStatePending // 待转账
- // 修改订单状态
- err = dbTx.Create(&withdrawOrder).Error
- if err != nil {
- dbTx.Rollback()
- return err
- }
- // 创建流水 冻结可用余额
- err = s.GenBillAndActionAsset(dbTx, userAsset.UserId, userAsset.Symbol, decimal.Zero.Sub(amount), amount, constant.BsById(constant.BsAssetWithdrawApply))
- if err != nil {
- dbTx.Rollback()
- return err
- }
- dbTx.Commit()
- // 发送转账处理信号
- go s.handleAllWithdraw()
- return nil
- }
- // 处理提现
- var withdrawIsRun = false
- func (s *CommonService) handleAllWithdraw() {
- // 使用内部锁
- if withdrawIsRun {
- return
- }
- withdrawIsRun = true
- defer func() {
- withdrawIsRun = false
- }()
- // 处理审核通过转账
- if err := s.handWithdrawPassOrder(); err != nil {
- core.Log.Errorf("处理审核通过转账 error:%s", err.Error())
- }
- // 处理驳回与系统失败转账
- if err := s.handWithdrawFailOrder(); err != nil {
- core.Log.Errorf("处理驳回与系统失败转账 error:%s", err.Error())
- }
- }
- // 处理提现订单 -- 状态为审核通过的产品
- func (s *CommonService) handWithdrawPassOrder() error {
- // 收到信号即处理全部订单
- items, err := s.BatchTransferRecord(s.DB().
- Where("status", constant.RwStatePass).
- Where("direction", constant.RwDirectionWithdraw).
- Where("transfer_state", constant.WithdrawStatePending))
- if err != nil {
- return err
- }
- for _, item := range items {
- item.WithdrawState = constant.WithdrawStateFinish
- _, err = s.withdrawToEx(item)
- if err != nil {
- // 资金退回处理
- item.Describe = fmt.Sprintf("%s\n%s", item.Describe, err.Error())
- core.Log.Errorf("系统 to 交易所 转账失败:%s", err.Error())
- item.Status = constant.RwStateSystemFail
- }
- if err = s.DB().Model(&entity.AssetRwRecord{}).Where("id", item.Id).Updates(
- map[string]interface{}{
- "status": item.Status,
- "describe": item.Describe,
- }).Error; err != nil {
- core.Log.Errorf("转出订单处理失败:%s 存储失败", err.Error())
- }
- }
- return nil
- }
- // 向交易所发起转账申请
- func (s *CommonService) withdrawToEx(req *entity.AssetRwRecord) (int, error) {
- // 这里仅做发起交易
- resp, err := exchange.TransferOut(req.OpenId, &exchange.TransferOutReq{
- Currency: req.Symbol,
- Amount: req.RealAmount.String(),
- OrderId: req.OrderId,
- })
- if err != nil {
- return 0, err
- }
- if resp.Code == 10000 {
- return resp.Code, errors.New(resp.Msg)
- }
- return resp.Code, nil
- }
- // 驳回订单处理
- func (s *CommonService) handWithdrawFailOrder() error {
- // 收到信号即处理全部订单
- itemsReject, err := s.BatchTransferRecord(s.DB().
- Where("status in (?,?)", constant.RwStateReject, constant.RwStateSystemFail).
- Where("direction", constant.RwDirectionWithdraw))
- if err != nil {
- return err
- }
- for _, item := range itemsReject {
- err = s.returnWithdraw(item)
- if err != nil {
- core.Log.Errorf("转账失败订单 资金退回错误:%s", err.Error())
- }
- }
- return nil
- }
- // 解冻资产 提现退回
- func (s *CommonService) returnWithdraw(item *entity.AssetRwRecord) error {
- var err error
- dbTx := s.DB().Begin()
- userAsset, err := s.CheckUserAssetByUserIdSymbol(dbTx, item.UserId, item.Symbol)
- if err != nil {
- err = fmt.Errorf("asset error")
- dbTx.Rollback()
- return err
- }
- if userAsset.Frozen.LessThan(item.Amount) {
- err = fmt.Errorf("OrderId:%s资金错误:冻结金额:%s小于提现金额:%s 转账 ", item.OrderId, userAsset.Frozen, item.Amount)
- dbTx.Rollback()
- return err
- }
- if err = dbTx.Model(&entity.AssetRwRecord{}).Where("id", item.Id).
- Update("Status", constant.RwStateReturnSuccess).Error; err != nil {
- core.Log.Errorf("转账失败订单 资金退回错误:%s", err.Error())
- dbTx.Rollback()
- return err
- }
- // 修改用户余额 可用->冻结
- // 创建流水 冻结可用余额
- err = s.GenBillAndActionAsset(dbTx,
- userAsset.UserId,
- userAsset.Symbol,
- decimal.Zero,
- decimal.Zero.Sub(item.Amount),
- constant.BsById(constant.BsAssetWithdrawApply))
- if err != nil {
- dbTx.Rollback()
- return err
- }
- dbTx.Commit()
- return nil
- }
|