|
@@ -0,0 +1,240 @@
|
|
|
+import { Inject, Logger, Provide } from '@midwayjs/decorator';
|
|
|
+import { BaseService } from '@cool-midway/core';
|
|
|
+import { HttpService } from '@midwayjs/axios';
|
|
|
+import { ILogger } from '@midwayjs/logger';
|
|
|
+import { CoolCommException } from '@cool-midway/core';
|
|
|
+import * as _ from 'lodash';
|
|
|
+import { WithdrawChannelService } from './withdrawChannel';
|
|
|
+import { MerchantService } from './merchant';
|
|
|
+import { DispatchService } from './channels/dispatch';
|
|
|
+import { Utils } from '../../../comm/utils';
|
|
|
+import * as md5 from 'md5';
|
|
|
+import { WithdrawService } from './withdraw';
|
|
|
+import { MchBalanceService } from './mchBalance';
|
|
|
+
|
|
|
+@Provide()
|
|
|
+export class RepayService extends BaseService {
|
|
|
+ @Inject()
|
|
|
+ httpService: HttpService;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ withdrawChannelService: WithdrawChannelService;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ merchantService: MerchantService;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ withdrawService: WithdrawService;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ dispatchService: DispatchService;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ mchBalanceService: MchBalanceService;
|
|
|
+
|
|
|
+ @Logger()
|
|
|
+ logger: ILogger;
|
|
|
+
|
|
|
+ @Inject()
|
|
|
+ utils: Utils;
|
|
|
+
|
|
|
+ async query(payload) {
|
|
|
+ try {
|
|
|
+ this.logger.info('代付查询', JSON.stringify(payload));
|
|
|
+ await this.validQueryParam(payload);
|
|
|
+ const merchant = await this.validMerchant(payload);
|
|
|
+ await this.validSign(payload, merchant);
|
|
|
+ const order = await this.withdrawService.findByOutOrderNo(
|
|
|
+ payload.outOrderNo
|
|
|
+ );
|
|
|
+ if (!order || order.mchId !== payload.mchId) {
|
|
|
+ throw new Error('订单不存在');
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ mchId: payload.mchId,
|
|
|
+ orderNo: order.orderNo,
|
|
|
+ outOrderNo: payload.outOrderNo,
|
|
|
+ amount: order.amount,
|
|
|
+ status: order.status,
|
|
|
+ };
|
|
|
+ } catch (err) {
|
|
|
+ this.logger.error('查询失败', JSON.stringify(payload), err.message);
|
|
|
+ throw new CoolCommException(err.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async validQueryParam(payload) {
|
|
|
+ if (_.isEmpty(payload.mchId)) {
|
|
|
+ throw new Error('商户号【mchId】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.outOrderNo)) {
|
|
|
+ throw new Error('商户订单号【outOrderNo】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.sign)) {
|
|
|
+ throw new Error('签名【sign】不能为空');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async order(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);
|
|
|
+ await this.validSign(payload, merchant);
|
|
|
+ await this.validOutOrderNo(payload);
|
|
|
+ await this.validBalance(merchant, payload);
|
|
|
+ let channelCharge = (+payload.amount * +channel.rate) / 100 + +channel.basicFee;
|
|
|
+ if(channelCharge < payload.feeMin) {
|
|
|
+ channelCharge = payload.feeMin;
|
|
|
+ }
|
|
|
+ let charge = (+payload.amount * +merchant.withdrawRate) / 100 + +merchant.withdrawBasicFee;
|
|
|
+ if(charge < merchant.withdrawFeeMin) {
|
|
|
+ charge = merchant.withdrawFeeMin;
|
|
|
+ }
|
|
|
+ withdraw = {
|
|
|
+ orderNo: this.utils.createOrderNo('DS'),
|
|
|
+ outOrderNo: payload.outOrderNo,
|
|
|
+ traceNo: '',
|
|
|
+ code: channel.code,
|
|
|
+ mchId: merchant.mchId,
|
|
|
+ amount: payload.amount,
|
|
|
+ currency: payload.currency,
|
|
|
+ accountNo: payload.accountNo,
|
|
|
+ accountName: payload.accountName,
|
|
|
+ bankCode: payload.bankCode,
|
|
|
+ bankName: payload.bankName,
|
|
|
+ channelCharge,
|
|
|
+ charge,
|
|
|
+ notifyUrl: payload.notifyUrl,
|
|
|
+ status: 1,
|
|
|
+ remark: ''
|
|
|
+ };
|
|
|
+ await this.withdrawService.create(withdraw);
|
|
|
+ return {
|
|
|
+ orderNo: withdraw.orderNo,
|
|
|
+ outOrderNo: withdraw.outOrderNo,
|
|
|
+ currency: withdraw.currency,
|
|
|
+ status: withdraw.status
|
|
|
+ };
|
|
|
+ } 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async validParam(payload) {
|
|
|
+ if (_.isEmpty(payload.mchId)) {
|
|
|
+ throw new Error('商户号【mchId】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.outOrderNo)) {
|
|
|
+ throw new Error('商户订单号【outOrderNo】不能为空');
|
|
|
+ }
|
|
|
+ if (payload.outOrderNo.length < 8 || payload.outOrderNo.length > 32) {
|
|
|
+ throw new Error('商户订单号的长度范围为:【8,32】');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.notifyUrl)) {
|
|
|
+ throw new Error('代付成功回调地址【notifyUrl】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.amount + '')) {
|
|
|
+ throw new Error('支付金额【amount】不能为空');
|
|
|
+ }
|
|
|
+ if (+payload.amount <= 0) {
|
|
|
+ throw new Error('支付金额【amount】必须大于0');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.currency)) {
|
|
|
+ throw new Error('货币单位【currency】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.accountNo)) {
|
|
|
+ throw new Error('银行账号【accountNo】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.accountName)) {
|
|
|
+ throw new Error('收款人姓名【accountName】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.bankName)) {
|
|
|
+ throw new Error('银行名称【bankName】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.bankCode)) {
|
|
|
+ throw new Error('银行编码【bankCode】不能为空');
|
|
|
+ }
|
|
|
+ if (_.isEmpty(payload.sign)) {
|
|
|
+ throw new Error('签名【sign】不能为空');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async validPayWay(payload) {
|
|
|
+ if(!payload.payType) {
|
|
|
+ return {
|
|
|
+ code: '',
|
|
|
+ rate: 0,
|
|
|
+ basicFee: 0,
|
|
|
+ feeMin: 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+ const channel = await this.withdrawChannelService.queryByCode(payload.payType);
|
|
|
+ if (!channel) {
|
|
|
+ throw new Error('当前通道码不可用');
|
|
|
+ }
|
|
|
+ let max = channel.max || 0;
|
|
|
+ let min = channel.min || 0;
|
|
|
+ if (+payload.amount > +max || +payload.amount < +min) {
|
|
|
+ throw new Error(`单笔最大限额【${+min}, ${+max}】`);
|
|
|
+ }
|
|
|
+ return channel;
|
|
|
+ }
|
|
|
+
|
|
|
+ async validMerchant(payload) {
|
|
|
+ const merchant = await this.merchantService.getByMchId(payload.mchId);
|
|
|
+ if (!merchant) {
|
|
|
+ throw new Error('商户不存在');
|
|
|
+ }
|
|
|
+ if (+merchant.status !== 1) {
|
|
|
+ throw new Error('商户状态异常');
|
|
|
+ }
|
|
|
+ if (+merchant.withdrawStatus !== 1) {
|
|
|
+ throw new Error('商户代付状态异常');
|
|
|
+ }
|
|
|
+ return merchant;
|
|
|
+ }
|
|
|
+
|
|
|
+ async validSign(payload, merchant) {
|
|
|
+ const data = { ...payload };
|
|
|
+ const sign = data.sign;
|
|
|
+ delete data.sign;
|
|
|
+ const signStr = this.utils.signSort(data);
|
|
|
+ const verifySign = md5(signStr + `&key=${merchant.key}`);
|
|
|
+ this.logger.info(
|
|
|
+ '代付下单加密结果',
|
|
|
+ sign,
|
|
|
+ signStr,
|
|
|
+ verifySign,
|
|
|
+ sign === verifySign
|
|
|
+ );
|
|
|
+ if (sign !== verifySign) {
|
|
|
+ throw new Error(
|
|
|
+ '验签失败,请确认签名是否正确,正确待签名字符串为:' + signStr
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async validOutOrderNo(payload: any) {
|
|
|
+ const order = await this.withdrawService.findByOutOrderNo(payload.outOrderNo);
|
|
|
+ if (order) {
|
|
|
+ throw new Error('商户订单号已存在,请勿重复下单');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async validBalance(merchant, data) {
|
|
|
+ const mchBalance = await this.mchBalanceService.queryByMerchant(merchant.mchId);
|
|
|
+ const { balance = 0 } = mchBalance;
|
|
|
+ if(+data.amount > +balance) {
|
|
|
+ throw new Error('当前商户余额不足,请确认!当前商户余额:' + balance);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|