test 9 місяців тому
батько
коміт
2870c0d01e

+ 9 - 0
src/modules/dj/controller/admin/comm.ts

@@ -6,6 +6,7 @@ import { CurrencyService } from '../../service/currency';
 import { MerchantService } from '../../service/merchant';
 import { PayTypeService } from '../../service/payType';
 import { ChannelService } from '../../service/channel';
+import { WithdrawChannelService } from '../../service/withdrawChannel';
 
 @Provide()
 @CoolController()
@@ -25,6 +26,9 @@ export class OrdertController extends BaseController {
   @Inject()
   channelService: ChannelService;
 
+  @Inject()
+  withdrawChannelService: WithdrawChannelService;
+
   @Inject()
   baseSysConfService: BaseSysConfService;
 
@@ -58,4 +62,9 @@ export class OrdertController extends BaseController {
   async getChannels() {
     return this.ok(await this.channelService.getChannelList());
   }
+
+  @Post('/getWithdrawChannels', { summary: '代付通道列表' })
+  async getWithdrawChannels() {
+    return this.ok(await this.withdrawChannelService.getWithdrawChannelList());
+  }
 }

+ 13 - 0
src/modules/dj/controller/admin/mchWithdrawChannel.ts

@@ -0,0 +1,13 @@
+import { Provide } from '@midwayjs/decorator';
+import { CoolController, BaseController } from '@cool-midway/core';
+import { MchWithdrawChannelService } from '../../service/mchWithdrawChannel';
+import { MchWithdrawChannelEntity } from '../../entity/mchWithdrawChannel';
+
+@Provide()
+@CoolController({
+  api: ['add', 'delete', 'update', 'list', 'info', 'page'],
+  entity: MchWithdrawChannelEntity,
+  service: MchWithdrawChannelService
+})
+export class MchWithdrawChannelController extends BaseController {
+}

+ 1 - 1
src/modules/dj/controller/admin/merchant.ts

@@ -11,7 +11,7 @@ import { Utils } from '../../../../comm/utils';
   service: MerchantService,
   pageQueryOp: {
     keyWordLikeFields: ['mchId'],
-    select: ['a.id', 'a.userId', 'a.name', 'a.mchId', 'a.email', 'a.status', 'a.whiteIp', 'a.remark', 'a.createTime', 'a.key', 'a.secret', 'a.currency']
+    select: ['a.id', 'a.userId', 'a.name', 'a.mchId', 'a.email', 'a.status', 'a.whiteIp', 'a.remark', 'a.createTime', 'a.key', 'a.secret']
   },
   infoIgnoreProperty: ['fundPwd']
 })

+ 15 - 8
src/modules/dj/controller/admin/open.ts

@@ -26,6 +26,7 @@ import { ILogger } from '@midwayjs/logger';
 import { WithdrawService } from '../../service/withdraw';
 import { KycService } from '../../service/kyc';
 import { RepayService } from '../../service/repay';
+import { WhiteIpMiddleware } from '../../middleware/whiteIp';
 
 @Provide()
 @CoolController()
@@ -71,15 +72,21 @@ export class PayOpenController extends BaseController {
   }
 
   @CoolTag(TagTypes.IGNORE_TOKEN)
-  @Post('/withdraw', { summary: '发起代付' })
+  @Post('/withdraw', { summary: '发起代付', middleware: [WhiteIpMiddleware] })
   public async withdraw(@Body(ALL) payload: any) {
-    return this.ok(await this.repayService.order(payload));
+    return this.ok(await this.repayService.withdraw(payload));
   }
 
   @CoolTag(TagTypes.IGNORE_TOKEN)
-  @Post('/withdrawQuery', { summary: '代收查询' })
+  @Post('/withdrawQuery', { summary: '代付查询', middleware: [WhiteIpMiddleware]  })
   public async withdrawQuery(@Body(ALL) payload: any) {
-    return this.ok(await this.repayService.query(payload));
+    return this.ok(await this.repayService.queryWithdraw(payload));
+  }
+
+  @CoolTag(TagTypes.IGNORE_TOKEN)
+  @Post('/queryBalance', { summary: '余额查询', middleware: [WhiteIpMiddleware]  })
+  public async queryBalance(@Body(ALL) payload: any) {
+    return this.ok(await this.repayService.queryBalance(payload));
   }
 
   @CoolTag(TagTypes.IGNORE_TOKEN)
@@ -96,7 +103,7 @@ export class PayOpenController extends BaseController {
     const headers = this.ctx.headers;
     this.logger.error('SunCard收到交易订单回调数据', JSON.stringify(payload), headers);
     await this.orderService.handleNotify('SUN_CARD', payload, headers);
-    this.ctx.body = {   
+    this.ctx.body = {
       "is_success": "true",
       "message": "success"
     };
@@ -108,19 +115,19 @@ export class PayOpenController extends BaseController {
     const headers = this.ctx.headers;
     this.logger.error('hambit收到交易订单回调数据', JSON.stringify(payload), headers);
     await this.orderService.handleNotify('HB_BRA', payload, headers);
-    this.ctx.body = {   
+    this.ctx.body = {
       "code": "200",
       "success": "true"
     };
   }
-  
+
   @CoolTag(TagTypes.IGNORE_TOKEN)
   @Post('/hambit/inr/notifyOrder', { summary: '印度交易订单回调通知' })
   public async hambitInrNotifyOrderGet(@Body(ALL) payload: any) {
     const headers = this.ctx.headers;
     this.logger.error('hambit收到交易订单回调数据', JSON.stringify(payload), headers);
     await this.orderService.handleNotify('HB_INR', payload, headers);
-    this.ctx.body = {   
+    this.ctx.body = {
       "code": "200",
       "success": "true"
     };

+ 24 - 3
src/modules/dj/controller/admin/withdraw.ts

@@ -13,13 +13,34 @@ export class WithdrawController extends BaseController {
   @Inject()
   withdrawService: WithdrawService;
 
+  @Post('/summary', { summary: '统计' })
+  async summary() {
+    const { body } = this.baseCtx.request;
+    return this.ok(await this.withdrawService.summary(body));
+  }
+
   @Post('/query', { summary: '查单' })
   async query(@Body('id') id) {
     return this.ok(await this.withdrawService.queryByApi(id));
   }
+  
+  @Post('/notify', { summary: '通知' })
+  async notify(@Body('id') id) {
+    return this.ok(await this.withdrawService.notify(id));
+  }
 
-  @Post('/withdraw', { summary: '发起代付' })
-  async withdraw(@Body(ALL) payload: any) {
-    return this.ok(await this.withdrawService.withdraw(payload));
+  @Post('/code', { summary: '获取订单通道编码' })
+  async code(@Body('id') id) {
+    return this.ok(id);
   }
+
+  @Post('/mch', { summary: '获取订单商户编码' })
+  async mch(@Body('id') id) {
+    return this.ok(id);
+  }
+
+  // @Post('/withdraw', { summary: '发起代付' })
+  // async withdraw(@Body(ALL) payload: any) {
+  //   return this.ok(await this.withdrawService.withdraw(payload));
+  // }
 }

+ 0 - 1
src/modules/dj/entity/balance.ts

@@ -6,7 +6,6 @@ import { Column, Entity, Index } from 'typeorm';
  */
 @Entity('dj_balance')
 export class BalanceEntity extends BaseEntity {
-  @Index({ unique: true })
   @Column({ comment: '订单号' })
   orderNo: string;
 

+ 20 - 0
src/modules/dj/entity/mchWithdrawChannel.ts

@@ -0,0 +1,20 @@
+import { BaseEntity } from '@cool-midway/core';
+import { Column, Entity, Index } from 'typeorm';
+
+/**
+ * 字典信息
+ */
+@Entity('dj_mch_withdraw_channel')
+export class MchWithdrawChannelEntity extends BaseEntity {
+  @Column({ comment: '商户号' })
+  mchId: string;
+
+  @Column({ comment: '货币' })
+  currency: string;
+
+  @Column({ comment: '通道编码' })
+  code: string;
+
+  @Column({ comment: '状态 0-未启用 1 启用', type: 'tinyint', default: 1 })
+  status: number;
+}

+ 1 - 4
src/modules/dj/entity/merchant.ts

@@ -31,10 +31,7 @@ export class MerchantEntity extends BaseEntity {
   @Column({ comment: 'secret' })
   secret: string;
 
-  @Column({ comment: '可用币种' })
-  currency: string;
-
-  @Column({ comment: 'IP白名单', length: 1000, nullable: true })
+  @Column({ comment: 'IP白名单', length: 1000, default: '' })
   whiteIp: string;
 
   @Column({ comment: '备注', nullable: true })

+ 1 - 1
src/modules/dj/entity/rate.ts

@@ -15,7 +15,7 @@ export class RateEntity extends BaseEntity {
   @Column({ comment: '类型 1-代收 2-代付', default: 1 })
   type: number;
 
-  @Column({ comment: '支付方式'})
+  @Column({ comment: '支付方式', nullable: true})
   payType: string;
 
   @Column({ comment: '费率', type: 'decimal', precision: 10, scale: 2 })

+ 33 - 0
src/modules/dj/middleware/whiteIp.ts

@@ -0,0 +1,33 @@
+import { Middleware } from '@midwayjs/decorator';
+import * as _ from 'lodash';
+import { NextFunction, Context } from '@midwayjs/koa';
+import { IMiddleware, Inject } from '@midwayjs/core';
+import { MerchantService } from '../service/merchant';
+
+/**
+ * 日志中间件
+ */
+@Middleware()
+export class WhiteIpMiddleware implements IMiddleware<Context, NextFunction> {
+
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      const ip = ctx.req.headers['x-forwarded-for'] || ctx.req.socket.remoteAddress.replace('::ffff:', '')
+      const merchantService = await ctx.requestContext.getAsync(
+        MerchantService
+      );
+      const mchId = ctx.request.body['mchId'];
+      let merchant = null;
+      if (mchId) {
+        merchant = await merchantService.getByMchId(mchId);
+      }
+      if (!merchant) {
+        throw new Error('No Permission')
+      }
+      if (!merchant.whiteIp.split(',').includes(ip)) {
+        throw new Error('IP访问受限')
+      }
+      await next();
+    };
+  }
+}

+ 19 - 10
src/modules/dj/service/balance.ts

@@ -4,7 +4,6 @@ import { BaseService } from '@cool-midway/core';
 import { InjectEntityModel } from '@midwayjs/typeorm';
 import { Repository } from 'typeorm';
 import * as _ from 'lodash';
-import { BalanceQueue } from '../queue/balanceQueue';
 import { Utils } from '../../../comm/utils';
 import { MerchantEntity } from '../entity/merchant';
 import { WalletEntity } from '../entity/wallet';
@@ -28,23 +27,33 @@ export class BalanceService extends BaseService {
   walletEntity: Repository<WalletEntity>;
 
   async addBalance(param) {
-    const data = await this.walletEntity.findOneBy({
+    let wallet = await this.walletEntity.findOneBy({
       mchId: param.mchId,
       currency: param.currency
     });
-    if (data) {
-      await this.balanceEntity.insert(param);
-      data.balance = +data.balance + +param.amount;
-      await this.walletEntity.save(data);
-    } else {
-      await this.balanceEntity.insert(param);
-      await this.walletEntity.save({
+    if (!wallet) {
+      wallet = await this.walletEntity.save({
         mchId: param.mchId,
         currency: param.currency,
-        balance: +param.amount,
+        balance: 0,
         freeze: 0
       })
     }
+    if (+param.amount !== 0) {
+      await this.balanceEntity.insert({
+        orderNo: param.orderNo,
+        mchId: param.mchId,
+        type: param.type,
+        currency: param.currency,
+        amount: param.amount,
+        remark: param.remark
+      });
+      wallet.balance = +wallet.balance + +param.amount;
+      wallet.freeze = +wallet.freeze + +param.freeze;
+    } else {
+      wallet.freeze = +wallet.freeze + +param.freeze;
+    }
+    await this.walletEntity.save(wallet);
   }
 
   async page(query) {

+ 20 - 16
src/modules/dj/service/channels/dispatch.ts

@@ -4,6 +4,7 @@ import { SunPayService } from './sunpay';
 import { ChannelService } from '../channel';
 import { HambitBraService } from './hambitBra';
 import { HambitInrService } from './hambitInr';
+import { WithdrawChannelService } from '../withdrawChannel';
 
 @Provide()
 export class DispatchService extends BaseService {
@@ -12,7 +13,10 @@ export class DispatchService extends BaseService {
 
   @Inject()
   channelService: ChannelService;
-  
+
+  @Inject()
+  WithdrawChannelService: WithdrawChannelService;
+
   @Inject()
   hambitBraService: HambitBraService;
 
@@ -45,11 +49,11 @@ export class DispatchService extends BaseService {
       };
     }
     try {
-      const channel = await this.channelService.queryByCode(code);
-      if(!this[channel.service]) {
+      const channel = await this.WithdrawChannelService.queryByCode(code);
+      if (!this[channel.service] || !this[channel.service].queryBalance) {
         return {
           status: 0,
-          message: "渠道不存在"
+          message: "代付渠道不支持"
         }
       }
       return await this[channel.service].queryBalance();
@@ -60,12 +64,9 @@ export class DispatchService extends BaseService {
 
   async withdraw(data) {
     try {
-      const channel = await this.channelService.queryByCode(data.code);
-      if(!this[channel.service]) {
-        return {
-          status: 2,
-          message: "渠道不存在"
-        }
+      const channel = await this.WithdrawChannelService.queryByCode(data.code);
+      if (!this[channel.service] || !this[channel.service].withdraw) {
+        throw new Error('代付渠道不支持');
       }
       return await this[channel.service].withdraw(data);
     } catch (e) {
@@ -75,16 +76,19 @@ export class DispatchService extends BaseService {
 
   async queryWithdraw(data) {
     try {
-      const channel = await this.channelService.queryByCode(data.code);
-      if(!this[channel.service]) {
+      const channel = await this.WithdrawChannelService.queryByCode(data.code);
+      if (!this[channel.service] || !this[channel.service].queryWithdraw) {
         return {
-          status: 2,
-          message: "渠道不存在"
+          status: 1,
+          message: "代付渠道不支持"
         }
       }
       return await this[channel.service].queryWithdraw(data);
     } catch (e) {
-      throw new Error('代付查询接口失败,失败原因:' + e.message);
+      return {
+        status: 1,
+        message: e.message
+      }
     }
   }
 
@@ -99,7 +103,7 @@ export class DispatchService extends BaseService {
 
   async handleWithdrawNotify(code, data) {
     try {
-      const channel = await this.channelService.queryByCode(code);
+      const channel = await this.WithdrawChannelService.queryByCode(code);
       return await this[channel.service].handleWithdrawNotify(data);
     } catch (e) {
       throw new Error('代付回调处理接口失败,失败原因:' + e.message);

+ 11 - 0
src/modules/dj/service/mchChannel.ts

@@ -10,6 +10,17 @@ export class MchChannelService extends BaseService {
   @InjectEntityModel(MchChannelEntity)
   mchChannelEntity: Repository<MchChannelEntity>;
 
+  async page(query) {
+    let { mchId = '', payType = '', code = '', status = '' } = query;
+    const sql = `SELECT * FROM dj_mch_channel a WHERE 1=1
+      ${this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])}
+      ${this.setSql(payType, 'and a.payType like ?', [`%${payType}%`])}
+      ${this.setSql(code, 'and a.code like ?', [`%${code}%`])}
+      ${this.setSql(status, 'and a.status like ?', [`%${status}%`])}
+    `;
+    return this.sqlRenderPage(sql, query);
+  }
+
   async getByMchIdAndType(mchId, payType) {
     return await this.mchChannelEntity.findBy({
       mchId,

+ 33 - 0
src/modules/dj/service/mchWithdrawChannel.ts

@@ -0,0 +1,33 @@
+import { Provide } from '@midwayjs/decorator';
+import { BaseService } from '@cool-midway/core';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import * as _ from 'lodash';
+import { MchWithdrawChannelEntity } from '../entity/mchWithdrawChannel';
+
+@Provide()
+export class MchWithdrawChannelService extends BaseService {
+  @InjectEntityModel(MchWithdrawChannelEntity)
+  mchWithdrawChannelEntity: Repository<MchWithdrawChannelEntity>;
+
+  
+  async page(query) {
+    let { mchId = '', currency = '', code = '', status = '' } = query;
+    const sql = `SELECT * FROM dj_mch_withdraw_channel a WHERE 1=1
+      ${this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])}
+      ${this.setSql(currency, 'and a.code like ?', [`%${currency}%`])}
+      ${this.setSql(code, 'and a.code like ?', [`%${code}%`])}
+      ${this.setSql(status, 'and a.status like ?', [`%${status}%`])}
+    `;
+    return this.sqlRenderPage(sql, query);
+  }
+
+
+  async getByMchIdAndCurrency(mchId, currency) {
+    return await this.mchWithdrawChannelEntity.findOneBy({
+      mchId,
+      currency,
+      status: 1
+    })
+  }
+}

+ 2 - 1
src/modules/dj/service/order.ts

@@ -110,7 +110,8 @@ export class OrderService extends BaseService {
       mchId: order.mchId,
       type: 1,
       amount: +order.amount - +order.charge,
-      currency: order.currency
+      currency: order.currency,
+      freeze: 0
     });
     if (notify) {
       // 发起通知

+ 34 - 0
src/modules/dj/service/rate.ts

@@ -5,6 +5,8 @@ import { Repository } from 'typeorm';
 import * as _ from 'lodash';
 import { RateEntity } from '../entity/rate';
 import { WalletService } from './wallet';
+import { Utils } from '../../../comm/utils';
+import { MerchantEntity } from '../entity/merchant';
 
 @Provide()
 export class RateService extends BaseService {
@@ -14,6 +16,38 @@ export class RateService extends BaseService {
   @Inject()
   walletService: WalletService;
 
+  @Inject()
+  ctx;
+
+  @Inject()
+  utils: Utils;
+  
+  @InjectEntityModel(MerchantEntity)
+  merchantEntity: Repository<MerchantEntity>;
+
+  async page(query) {
+    let { mchId = '', currency = '', type = '' } = query;
+    const { roleIds, userId } = this.ctx.admin;
+    if (this.utils.isMerchant(roleIds)) {
+      const merchant = await this.merchantEntity.findOneBy({ userId });
+      if (merchant) {
+        mchId = merchant.mchId;
+      } else {
+        mchId = '-1';
+      }
+    }
+    const sql = `SELECT * FROM dj_rate a WHERE 1=1
+      ${this.utils.isMerchant(roleIds)
+        ? this.setSql(mchId, 'and a.mchId = ?', [`${mchId}`])
+        : this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])
+      }
+      ${this.setSql(currency, 'and a.currency like ?', [`%${currency}%`])}
+      ${this.setSql(type, 'and a.type like ?', [`%${type}%`])}
+    `;
+    return this.sqlRenderPage(sql, query);
+  }
+
+
   async add(param) {
     const data = await this.rateEntity.findOneBy({
       mchId: param.mchId,

+ 103 - 37
src/modules/dj/service/repay.ts

@@ -11,15 +11,24 @@ import { Utils } from '../../../comm/utils';
 import * as md5 from 'md5';
 import { WithdrawService } from './withdraw';
 import { WalletService } from './wallet';
+import { MchWithdrawChannelService } from './mchWithdrawChannel';
+import { RateService } from './rate';
+import { WithdrawStateService } from './withdrawState';
 
 @Provide()
 export class RepayService extends BaseService {
   @Inject()
   httpService: HttpService;
 
+  @Inject()
+  withdrawStateService: WithdrawStateService;
+
   @Inject()
   withdrawChannelService: WithdrawChannelService;
 
+  @Inject()
+  mchWithdrawChannelService: MchWithdrawChannelService;
+
   @Inject()
   merchantService: MerchantService;
 
@@ -32,17 +41,50 @@ export class RepayService extends BaseService {
   @Inject()
   walletService: WalletService;
 
+  @Inject()
+  rateService: RateService;
+
   @Logger()
   logger: ILogger;
 
   @Inject()
   utils: Utils;
 
-  async query(payload) {
+  async queryBalance(payload) {
+    this.logger.info('余额查询', JSON.stringify(payload));
+    await this.validQueryBalanceParam(payload);
+    const merchant = await this.validMerchant(payload.mchId);
+    await this.validSign(payload, merchant);
+    let wallet;
+    if (payload.currency) {
+      wallet = [await this.walletService.queryByMchIdAndCurrency(payload.mchId, payload.currency)];
+    } else {
+      wallet = await this.walletService.queryByMchId(payload.mchId);
+    }
+    return wallet.map(item => {
+      return {
+        currency: item.currency,
+        balance: item.balance,
+        withdrawBalance: (+item.balance - +item.freeze).toFixed(2),
+        freeze: item.freeze,
+      }
+    })
+  }
+
+  async validQueryBalanceParam(payload) {
+    if (_.isEmpty(payload.mchId)) {
+      throw new Error('商户号【mchId】不能为空');
+    }
+    if (_.isEmpty(payload.sign)) {
+      throw new Error('签名【sign】不能为空');
+    }
+  }
+
+  async queryWithdraw(payload) {
     try {
       this.logger.info('代付查询', JSON.stringify(payload));
       await this.validQueryParam(payload);
-      const merchant = await this.validMerchant(payload);
+      const merchant = await this.validMerchant(payload.mchId);
       await this.validSign(payload, merchant);
       const order = await this.withdrawService.findByOutOrderNo(
         payload.outOrderNo
@@ -56,6 +98,8 @@ export class RepayService extends BaseService {
         outOrderNo: payload.outOrderNo,
         amount: order.amount,
         status: order.status,
+        charge: order.charge,
+        remark: order.remark,
       };
     } catch (err) {
       this.logger.error('查询失败', JSON.stringify(payload), err.message);
@@ -75,24 +119,25 @@ export class RepayService extends BaseService {
     }
   }
 
-  async order(payload) {
+  async withdraw(payload) {
     let withdraw: any = null;
     try {
       this.logger.info('代付下单', JSON.stringify(payload));
       await this.validParam(payload);
-      const merchant = await this.validMerchant(payload);
-      const channel = await this.validPayWay(payload);
+      const merchant = await this.validMerchant(payload.mchId);
+      const rate = await this.validRate(payload.mchId, payload.currency);
       await this.validSign(payload, merchant);
-      await this.validOutOrderNo(payload);
-      await this.validBalance(merchant, payload);
+      await this.validOutOrderNo(payload.outOrderNo);
+      const channel = await this.getChannel(merchant.mchId, payload.currency, payload.amount);
       let channelCharge = (+payload.amount * +channel.rate) / 100 + +channel.basicFee;
-      if(channelCharge < payload.feeMin) {
-        channelCharge = payload.feeMin;
+      if (channelCharge < channel.feeMin) {
+        channelCharge = channel.feeMin;
       }
-      let charge = (+payload.amount * +merchant.withdrawRate) / 100 + +merchant.withdrawBasicFee;
-      if(charge < merchant.withdrawFeeMin) {
-        charge = merchant.withdrawFeeMin;
+      let charge = (+payload.amount * +rate.rate) / 100 + +rate.basicFee;
+      if (charge < rate.feeMin) {
+        charge = rate.feeMin;
       }
+      await this.validBalance(merchant, payload.currency, payload.amount + charge);
       withdraw = {
         orderNo: this.utils.createOrderNo('DS'),
         outOrderNo: payload.outOrderNo,
@@ -108,10 +153,21 @@ export class RepayService extends BaseService {
         channelCharge,
         charge,
         notifyUrl: payload.notifyUrl,
-        status: 1,
+        status: this.withdrawStateService.STATUS.ACCEPT,
         remark: ''
       };
       await this.withdrawService.create(withdraw);
+      await this.walletService.updateBalance({
+        orderNo: withdraw.orderNo,
+        mchId: withdraw.mchId,
+        type: 2,
+        amount: 0,
+        currency: withdraw.currency,
+        freeze: (+withdraw.amount + +withdraw.charge)
+      })
+      if (withdraw.code) {
+        this.withdrawStateService.stateTo(withdraw.status, this.withdrawStateService.STATUS.PROCESSIONG, withdraw);
+      }
       return {
         orderNo: withdraw.orderNo,
         outOrderNo: withdraw.outOrderNo,
@@ -120,11 +176,6 @@ export class RepayService extends BaseService {
       };
     } catch (err) {
       this.logger.error('代付失败', JSON.stringify(payload), err.message);
-      if (withdraw) {
-        withdraw.remark = err.message;
-        withdraw.status = 3;
-        await this.withdrawService.create(withdraw);
-      }
       throw new CoolCommException(err.message);
     }
   }
@@ -143,10 +194,10 @@ export class RepayService extends BaseService {
       throw new Error('代付成功回调地址【notifyUrl】不能为空');
     }
     if (_.isEmpty(payload.amount + '')) {
-      throw new Error('支付金额【amount】不能为空');
+      throw new Error('金额【amount】不能为空');
     }
     if (+payload.amount <= 0) {
-      throw new Error('支付金额【amount】必须大于0');
+      throw new Error('金额【amount】必须大于0');
     }
     if (_.isEmpty(payload.currency)) {
       throw new Error('货币单位【currency】不能为空');
@@ -168,8 +219,9 @@ export class RepayService extends BaseService {
     }
   }
 
-  async validPayWay(payload) {
-    if(!payload.payType) {
+  async getChannel(mchId, currency, amount) {
+    const mchWithdrawChannel = await this.mchWithdrawChannelService.getByMchIdAndCurrency(mchId, currency);
+    if (!mchWithdrawChannel) {
       return {
         code: '',
         rate: 0,
@@ -177,32 +229,46 @@ export class RepayService extends BaseService {
         feeMin: 0
       };
     }
-    const channel = await this.withdrawChannelService.queryByCode(payload.payType);
+    const channel = await this.withdrawChannelService.queryByCode(mchWithdrawChannel.code);
     if (!channel) {
-      throw new Error('当前通道码不可用');
+      return {
+        code: '',
+        rate: 0,
+        basicFee: 0,
+        feeMin: 0
+      };
     }
     let max = channel.max || 0;
     let min = channel.min || 0;
-    if (+payload.amount > +max || +payload.amount < +min) {
+    if (+amount > +max || +amount < +min) {
       throw new Error(`单笔最大限额【${+min}, ${+max}】`);
     }
     return channel;
   }
 
-  async validMerchant(payload) {
-    const merchant = await this.merchantService.getByMchId(payload.mchId);
+  async validMerchant(mchId) {
+    const merchant = await this.merchantService.getByMchId(mchId);
     if (!merchant) {
       throw new Error('商户不存在');
     }
     if (+merchant.status !== 1) {
       throw new Error('商户状态异常');
     }
-    if (+merchant.withdrawStatus !== 1) {
-      throw new Error('商户代付状态异常');
-    }
     return merchant;
   }
 
+  async validRate(mchId, currency) {
+    const rate = await this.rateService.getOne({
+      mchId,
+      currency,
+      type: 2
+    });
+    if (!rate) {
+      throw new Error('商户未开通代付渠道');
+    }
+    return rate;
+  }
+
   async validSign(payload, merchant) {
     const data = { ...payload };
     const sign = data.sign;
@@ -223,18 +289,18 @@ export class RepayService extends BaseService {
     }
   }
 
-  async validOutOrderNo(payload: any) {
-    const order = await this.withdrawService.findByOutOrderNo(payload.outOrderNo);
+  async validOutOrderNo(outOrderNo) {
+    const order = await this.withdrawService.findByOutOrderNo(outOrderNo);
     if (order) {
       throw new Error('商户订单号已存在,请勿重复下单');
     }
   }
 
-  async validBalance(merchant, data) {
-    const wallet = await this.walletService.queryByMerchant(merchant.mchId);
-    const { balance = 0 } = wallet;
-    if(+data.amount > +balance) {
-      throw new Error('当前商户余额不足,请确认!当前商户余额:' + balance);
+  async validBalance(merchant, currency, amount) {
+    const wallet = await this.walletService.queryByMchIdAndCurrency(merchant.mchId, currency);
+    const { balance = 0, freeze = 0 } = wallet;
+    if (+amount > +balance - +freeze) {
+      throw new Error('当前商户余额不足,请确认!当前商户可提现余额:' + (+balance - +freeze));
     }
   }
 }

+ 11 - 2
src/modules/dj/service/wallet.ts

@@ -66,7 +66,10 @@ export class WalletService extends BaseService {
   }
 
   async update(param) {
-    await this.updateBalance(param);
+    await this.updateBalance({
+      ...param,
+      freeze: 0
+    });
   }
 
   async updateBalance(param) {
@@ -90,8 +93,14 @@ export class WalletService extends BaseService {
     }
   }
 
-  async queryByMerchant(mchId) {
+  async queryByMchIdAndCurrency(mchId, currency) {
     return this.walletEntity.findOneBy({
+      mchId, currency
+    })
+  }
+
+  async queryByMchId(mchId) {
+    return this.walletEntity.findBy({
       mchId
     })
   }

+ 177 - 49
src/modules/dj/service/withdraw.ts

@@ -7,7 +7,10 @@ import { WithdrawEntity } from '../entity/withdraw';
 import { DispatchService } from './channels/dispatch';
 import { CoolCommException } from '@cool-midway/core';
 import { Utils } from '../../../comm/utils';
-import { WalletService } from './wallet';
+import moment = require('moment');
+import { MerchantEntity } from '../entity/merchant';
+import { WithdrawStateService } from './withdrawState';
+import { WithdrawNotifyService } from './withdrawNotify';
 
 @Provide()
 export class WithdrawService extends BaseService {
@@ -21,42 +24,159 @@ export class WithdrawService extends BaseService {
   utils: Utils;
 
   @Inject()
-  walletService: WalletService;
+  ctx;
 
-  async create(withdraw: WithdrawEntity) {
-    if (withdraw.id) {
-      withdraw.updateTime = new Date();
-      await this.withdrawEntity.update(withdraw.id, withdraw);
-    } else {
-      withdraw.createTime = new Date();
-      withdraw.updateTime = new Date();
-      await this.withdrawEntity.insert(withdraw);
+  @InjectEntityModel(MerchantEntity)
+  merchantEntity: Repository<MerchantEntity>;
+
+  @Inject()
+  withdrawStateService: WithdrawStateService;
+
+  @Inject()
+  withdrawNotifyService: WithdrawNotifyService
+
+  async summary(query) {
+    let {
+      orderNo = '',
+      outOrderNo = '',
+      traceNo = '',
+      mchId = '',
+      code = '',
+      status = '',
+      createTime = [],
+    } = query;
+    if (!createTime || createTime.length !== 2) {
+      return {
+        num1: 0,
+        num2: 0,
+        num3: 0,
+        num4: 0
+      };
+    }
+    if (moment(createTime[1]).diff(moment(createTime[0]), 'days') > 30) {
+      return {
+        num1: 0,
+        num2: 0,
+        num3: 0,
+        num4: 0
+      };
+    }
+    const { roleIds, userId } = this.ctx.admin;
+    if (this.utils.isMerchant(roleIds)) {
+      const merchant = await this.merchantEntity.findOneBy({ userId });
+      if (merchant) {
+        mchId = merchant.mchId;
+      } else {
+        mchId = '-1';
+      }
     }
-    return withdraw.id;
+    const sql = `SELECT 
+    SUM(IF(a.status = 8, a.amount, 0)) as num1,
+    SUM(IF(a.status = 8, a.charge, 0)) as num2,
+    SUM(IF(a.status = 8, 1, 0)) as num3,
+    ${this.utils.isMerchant(roleIds)
+        ? ''
+        : ',SUM(IF(a.status = 8, a.channelCharge, 0)) as num4'
+      }
+    FROM dj_withdraw a WHERE 1=1
+      ${this.setSql(orderNo, 'and a.orderNo like ?', [`%${orderNo}%`])}
+      ${this.setSql(outOrderNo, 'and a.outOrderNo like ?', [`%${outOrderNo}%`])}
+      ${this.setSql(traceNo, 'and a.traceNo like ?', [`%${traceNo}%`])}
+      ${this.utils.isMerchant(roleIds)
+        ? this.setSql(mchId, 'and a.mchId = ?', [`${mchId}`])
+        : this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])
+      }
+      ${this.setSql(code, 'and a.code = ?', [code])}
+      ${this.setSql(status, 'and a.status = ?', [status])}
+      ${this.setSql(
+        createTime && createTime.length === 2,
+        'and (a.createTime between ? and ?)',
+        createTime
+      )}
+    `;
+    return this.nativeQuery(sql);
+  }
+
+  async page(query) {
+    let {
+      orderNo = '',
+      outOrderNo = '',
+      traceNo = '',
+      mchId = '',
+      code = '',
+      status = '',
+      createTime = [],
+    } = query;
+    if (!createTime || createTime.length !== 2) {
+      throw new CoolCommException('请选择日期范围');
+    }
+    if (moment(createTime[1]).diff(moment(createTime[0]), 'days') > 30) {
+      throw new CoolCommException('日期范围最大只能30天');
+    }
+    const { roleIds, userId } = this.ctx.admin;
+    if (this.utils.isMerchant(roleIds)) {
+      const merchant = await this.merchantEntity.findOneBy({ userId });
+      if (merchant) {
+        mchId = merchant.mchId;
+      } else {
+        mchId = '-1';
+      }
+    }
+    const sql = `SELECT a.id, a.orderNo, a.outOrderNo, a.traceNo, a.mchId,  a.code, a.amount, a.currency, 
+    ${this.utils.isMerchant(roleIds) ? '' : 'a.channelCharge,'} 
+    a.charge, a.status, a.notifyUrl,  a.remark, a.createTime, a.updateTime, a.accountNo, a.accountName, a.bankName, a.bankCode 
+    FROM dj_withdraw a WHERE 1=1
+      ${this.setSql(orderNo, 'and a.orderNo like ?', [`%${orderNo}%`])}
+      ${this.setSql(outOrderNo, 'and a.outOrderNo like ?', [`%${outOrderNo}%`])}
+      ${this.setSql(traceNo, 'and a.traceNo like ?', [`%${traceNo}%`])}
+      ${this.utils.isMerchant(roleIds)
+        ? this.setSql(mchId, 'and a.mchId = ?', [`${mchId}`])
+        : this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])
+      }
+      ${this.setSql(code, 'and a.code = ?', [code])}
+      ${this.setSql(status, 'and a.status = ?', [status])}
+      ${this.setSql(
+        createTime && createTime.length === 2,
+        'and (a.createTime between ? and ?)',
+        createTime
+      )}
+    `;
+    return this.sqlRenderPage(sql, query);
   }
 
   async findByOutOrderNo(outOrderNo: any) {
     return await this.withdrawEntity.findOneBy({ outOrderNo });
   }
 
-  async handleNotify(code, payload) {
-    const data = await this.dispatchService.handleWithdrawNotify(code, payload);
-    const withdraw = await this.withdrawEntity.findOneBy({
-      orderNo: data.orderNo,
-    });
-    await this.updateWithdraw(withdraw, data);
+  async create(withdraw: WithdrawEntity) {
+    withdraw.createTime = new Date();
+    withdraw.updateTime = new Date();
+    return await this.withdrawEntity.insert(withdraw);
   }
 
-  async withdraw(payload) {
-    payload.orderNo = this.utils.createOrderNo('XF');
-    payload.status = 0;
-    await this.withdrawEntity.insert(payload);
-    const result = await this.dispatchService.withdraw(payload);
-    if (result.status === 2) {
-      payload.status = 2;
-      payload.remark = result.message + '\n' + payload.remark;
+  async update(param) {
+    const { roleIds } = this.ctx.admin;
+    if (this.utils.isMerchant(roleIds)) {
+      return;
+    }
+    const withdraw = await this.withdrawEntity.findOneBy({ id: param.id });
+    if (!withdraw) {
+      throw new Error('订单不存在');
+    }
+    withdraw.accountNo = param.accountNo;
+    withdraw.accountName = param.accountName;
+    withdraw.bankCode = param.bankCode;
+    withdraw.bankName = param.bankName;
+    withdraw.remark = param.remark;
+    const status = +param.status;
+    if (status === this.withdrawStateService.STATUS.PROCESSIONG) {
+      withdraw.code = param.code;
+    }
+    if (status === this.withdrawStateService.STATUS.SUCCESS) {
+      withdraw.code = param.code;
+      withdraw.traceNo = param.traceNo;
     }
-    return result;
+    return await this.withdrawStateService.stateTo(withdraw.status, status, withdraw);
   }
 
   async queryByApi(id) {
@@ -64,39 +184,47 @@ export class WithdrawService extends BaseService {
     if (!withdraw) {
       throw new CoolCommException('订单不存在');
     }
-    if (+withdraw.status === 0) {
+    if (+withdraw.status === this.withdrawStateService.STATUS.PROCESSIONG) {
       const data = await this.dispatchService.queryWithdraw(withdraw);
-      if (+data.status === 1 || +data.status === 2) {
+      if (+data.status === this.withdrawStateService.STATUS.SUCCESS
+        || +data.status === this.withdrawStateService.STATUS.FAIL) {
         // 更新订单和更新余额
-        await this.updateWithdraw(withdraw, data);
+        if (data.traceNo) {
+          withdraw.traceNo = data.traceNo
+        }
+        if (data.message) {
+          withdraw.remark = data.message + '\n' + withdraw.remark;
+        }
+        await this.withdrawStateService.stateTo(+withdraw.status, +data.status, withdraw);
       }
       return data;
     } else {
-      return withdraw.status;
+      return {
+        status: 1,
+        message: '订单状态异常'
+      };
     }
   }
 
-  async updateWithdraw(bean, payload) {
-    const withdraw = await this.withdrawEntity.findOneBy({ id: bean.id });
-    if (!withdraw || +withdraw.status === 1) {
-      // 订单早就已成功,无需做任何操作
-      return;
+  async handleWithdrawNotify(code, payload) {
+    const data = await this.dispatchService.handleWithdrawNotify(code, payload);
+    const withdraw = await this.withdrawEntity.findOneBy({
+      orderNo: data.orderNo,
+    });
+    if (data.traceNo) {
+      withdraw.traceNo = data.traceNo
     }
-    withdraw.status = payload.status;
-    withdraw.updateTime = new Date();
-    if (payload.message) {
-      withdraw.remark = payload.message + '\n' + payload.remark;
+    if (data.message) {
+      withdraw.remark = data.message + '\n' + withdraw.remark;
     }
-    withdraw.traceNo = payload.traceNo;
-    await this.withdrawEntity.update(withdraw.id, withdraw);
-    if (withdraw.mchId && +withdraw.status === 1) {
-      await this.walletService.updateBalance({
-        orderNo: withdraw.orderNo,
-        mchId: withdraw.mchId,
-        type: 2,
-        amount: -withdraw.amount,
-      });
+    await this.withdrawStateService.stateTo(withdraw.status, +data.status, withdraw);
+  }
+
+  async notify(id) {
+    const withdraw = await this.withdrawEntity.findOneBy({ id });
+    if (withdraw && +withdraw.status === this.withdrawStateService.STATUS.SUCCESS
+      || withdraw && +withdraw.status === this.withdrawStateService.STATUS.FAIL) {
+      return await this.withdrawNotifyService.notify(withdraw, true);
     }
-    return true;
   }
 }

+ 9 - 0
src/modules/dj/service/withdrawChannel.ts

@@ -16,4 +16,13 @@ export class WithdrawChannelService extends BaseService {
       code,
     });
   }
+
+  async getWithdrawChannelList() {
+    return await this.withdrawChannelEntity.find({
+      where: {
+        status: 1
+      },
+      select: ['name', 'code', 'currency']
+    })
+  }
 }

+ 113 - 0
src/modules/dj/service/withdrawNotify.ts

@@ -0,0 +1,113 @@
+import { Inject, Logger, Provide } from '@midwayjs/decorator';
+import { BaseService } from '@cool-midway/core';
+import { HttpService } from '@midwayjs/axios';
+import { ILogger } from '@midwayjs/logger';
+import * as _ from 'lodash';
+import { Utils } from '../../../comm/utils';
+import * as md5 from 'md5';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { MerchantEntity } from '../entity/merchant';
+import { Repository } from 'typeorm';
+import { WithdrawEntity } from '../entity/withdraw';
+
+const notifyTimes = [5, 10, 15, 30, 60, 120, 300, 600, 1200];
+
+@Provide()
+export class WithdrawNotifyService extends BaseService {
+  @Inject()
+  httpService: HttpService;
+
+  @Logger()
+  logger: ILogger;
+
+  @Inject()
+  utils: Utils;
+
+  @InjectEntityModel(MerchantEntity)
+  merchantEntity: Repository<MerchantEntity>;
+
+  async notifyReq(url, data, idx = 1) {
+    try {
+      this.logger.info(`第${idx}次代付回调通知`, url, JSON.stringify(data));
+      const result = await this.httpService.post(url, data);
+      if (result.data === 'success') {
+        return {
+          status: 1,
+        };
+      } else {
+        this.logger.error(
+          `第${idx}次代付回调通知失败`,
+          url,
+          JSON.stringify(data),
+          result.data
+        );
+        return {
+          status: 0,
+          msg: result.data,
+        };
+      }
+    } catch (err) {
+      this.logger.error(
+        `第${idx}次代付回调通知失败`,
+        url,
+        JSON.stringify(data),
+        err.message
+      );
+      return {
+        status: 0,
+        msg: err.message,
+      };
+    }
+  }
+
+  async notify(withdrawOrder: WithdrawEntity, isOnce = false) {
+    try {
+      const merchant = await this.merchantEntity.findOneBy({
+        mchId: withdrawOrder.mchId,
+      });
+      if (!merchant) {
+        throw new Error('商户不存在');
+      }
+      const url = withdrawOrder.notifyUrl;
+      const params: any = {
+        mchId: withdrawOrder.mchId,
+        orderNo: withdrawOrder.orderNo,
+        outOrderNo: withdrawOrder.outOrderNo,
+        amount: withdrawOrder.amount,
+        charge: withdrawOrder.charge,
+        status: withdrawOrder.status,
+        remark: withdrawOrder.remark
+      };
+      const signStr = this.utils.signSort(params);
+      params.sign = md5(signStr + `&key=${merchant.key}`);
+      if (isOnce) {
+        return await this.notifyReq(url, params);
+      } else {
+        this.notifySchedule(url, params);
+        return {
+          status: 1,
+        };
+      }
+    } catch (err) {
+      this.logger.error('代付回调通知失败', withdrawOrder.orderNo, err.message);
+    }
+  }
+
+  async notifySchedule(url, params, idx = -1) {
+    if (idx >= notifyTimes.length) {
+      return;
+    }
+    const res = await this.notifyReq(url, params, idx + 2);
+    if (res && +res.status === 0) {
+      setTimeout(
+        (url, params, idx) => {
+          this.notifySchedule(url, params, idx + 1);
+        },
+        notifyTimes[idx + 1] * 1000,
+        url,
+        params,
+        idx
+      );
+    }
+  }
+}

+ 183 - 0
src/modules/dj/service/withdrawState.ts

@@ -0,0 +1,183 @@
+import { Inject, Logger, Provide } from '@midwayjs/decorator';
+import { BaseService } from '@cool-midway/core';
+import { WithdrawEntity } from '../entity/withdraw';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { ILogger } from '@midwayjs/logger';
+import { WalletService } from './wallet';
+import { DispatchService } from './channels/dispatch';
+import { WithdrawNotifyService } from './withdrawNotify';
+
+@Provide()
+export class WithdrawStateService extends BaseService {
+    @InjectEntityModel(WithdrawEntity)
+    withdrawEntity: Repository<WithdrawEntity>;
+
+    @Inject()
+    withdrawNotifyService: WithdrawNotifyService;
+
+    @Inject()
+    walletService: WalletService;
+
+    @Inject()
+    dispatchService: DispatchService;
+
+    @Logger()
+    logger: ILogger;
+
+    STATUS = {
+        SUCCESS: 8,
+        FAIL: 3,
+        ACCEPT: 1,
+        PROCESSIONG: 2
+    };
+    EVENT = {
+        TO_SUCCESS: 'toSuccess',
+        FAIL_TO_SUCCESS: 'failToSuccess',
+        TO_FAIL: 'toFail',
+        SUCCESS_TO_FAIL: 'successToFail',
+        ACCEPT_TO_PROCESSIONG: 'acceptToProcessiong',
+        FAIL_TO_WAIT: 'failToWait',
+        SUCCESS_TO_WAIT: 'successToWait'
+    };
+    async stateTo(oldStatus, newStatus, withdraw: WithdrawEntity) {
+        this.logger.info(withdraw.orderNo, `from ${oldStatus} to ${newStatus}`);
+        withdraw.status = +newStatus;
+        withdraw.updateTime = new Date();
+        switch (+oldStatus) {
+            case this.STATUS.ACCEPT: {
+                if (+newStatus === this.STATUS.PROCESSIONG) {
+                    return await this.dispatch(this.EVENT.ACCEPT_TO_PROCESSIONG, withdraw);
+                } else if (+newStatus === this.STATUS.FAIL) {
+                    return await this.dispatch(this.EVENT.TO_FAIL, withdraw);
+                } else if (+newStatus === this.STATUS.SUCCESS) {
+                    return await this.dispatch(this.EVENT.TO_SUCCESS, withdraw);
+                }
+                break;
+            }
+            case this.STATUS.PROCESSIONG: {
+                if (+newStatus === this.STATUS.FAIL) {
+                    return await this.dispatch(this.EVENT.TO_FAIL, withdraw);
+                } else if (+newStatus === this.STATUS.SUCCESS) {
+                    return await this.dispatch(this.EVENT.TO_SUCCESS, withdraw);
+                }
+                break;
+            }
+            case this.STATUS.FAIL: {
+                if (+newStatus === this.STATUS.SUCCESS) {
+                    return await this.dispatch(this.EVENT.FAIL_TO_SUCCESS, withdraw);
+                } else if (+newStatus === this.STATUS.ACCEPT || +newStatus === this.STATUS.PROCESSIONG) {
+                    return await this.dispatch(this.EVENT.FAIL_TO_WAIT, withdraw);
+                }
+                break;
+            }
+            case this.STATUS.SUCCESS: {
+                if (+newStatus === this.STATUS.FAIL) {
+                    return await this.dispatch(this.EVENT.SUCCESS_TO_FAIL, withdraw);
+                } else if (+newStatus === this.STATUS.ACCEPT || +newStatus === this.STATUS.PROCESSIONG) {
+                    return await this.dispatch(this.EVENT.SUCCESS_TO_WAIT, withdraw);
+                }
+                break;
+            }
+        }
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+    }
+    async dispatch(event: string, withdraw: WithdrawEntity) {
+        return await this[event](withdraw);
+    }
+
+    // 订单成功
+    async toSuccess(withdraw) {
+        await this.walletService.updateBalance({
+            orderNo: withdraw.orderNo,
+            mchId: withdraw.mchId,
+            type: 2,
+            amount: -(+withdraw.amount + +withdraw.charge),
+            freeze: -(+withdraw.amount + +withdraw.charge),
+            currency: withdraw.currency
+        });
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+        this.withdrawNotifyService.notify(withdraw);
+
+    }
+
+    // 订单失败=>成功
+    async failToSuccess(withdraw) {
+        await this.walletService.updateBalance({
+            orderNo: withdraw.orderNo,
+            mchId: withdraw.mchId,
+            type: 2,
+            amount: -(+withdraw.amount + +withdraw.charge),
+            freeze: 0,
+            currency: withdraw.currency
+        });
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+        this.withdrawNotifyService.notify(withdraw);
+    }
+
+    // 订单失败
+    async toFail(withdraw) {
+        await this.walletService.updateBalance({
+            orderNo: withdraw.orderNo,
+            mchId: withdraw.mchId,
+            type: 2,
+            amount: 0,
+            freeze: -(+withdraw.amount + +withdraw.charge),
+            currency: withdraw.currency
+        });
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+        this.withdrawNotifyService.notify(withdraw);
+    }
+
+    // 订单成功=》失败
+    async successToFail(withdraw) {
+        await this.walletService.updateBalance({
+            orderNo: withdraw.orderNo,
+            mchId: withdraw.mchId,
+            type: 3,
+            amount: +withdraw.amount + +withdraw.charge,
+            freeze: 0,
+            currency: withdraw.currency
+        });
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+        this.withdrawNotifyService.notify(withdraw);
+    }
+
+    // 发起代付:已受理=》银行处理中
+    async acceptToProcessiong(withdraw) {
+        try {
+            const { traceNo } = await this.dispatchService.withdraw(withdraw);
+            withdraw.traceNo = traceNo;
+        } catch (err) {
+            withdraw.remark = err.message;
+        }
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+    }
+
+    // 重新发起:失败 =》等待
+    async failToWait(withdraw) {
+        await this.walletService.updateBalance({
+            orderNo: withdraw.orderNo,
+            mchId: withdraw.mchId,
+            type: 2,
+            amount: 0,
+            freeze: +withdraw.amount + +withdraw.charge,
+            currency: withdraw.currency
+        });
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+    }
+
+
+    // 重新发起:成功 =》等待
+    async successToWait(withdraw) {
+        await this.walletService.updateBalance({
+            orderNo: withdraw.orderNo,
+            mchId: withdraw.mchId,
+            type: 3,
+            amount: +withdraw.amount + +withdraw.charge,
+            freeze: +withdraw.amount + +withdraw.charge,
+            currency: withdraw.currency
+        });
+        await this.withdrawEntity.update(withdraw.id, withdraw);
+    }
+}