提现风控
This commit is contained in:
parent
7ff418ef44
commit
f37bc4779a
|
@ -68,7 +68,7 @@ public class IotConstants {
|
|||
public static final String COMMAND_RECHARGE = "time";
|
||||
|
||||
/**
|
||||
* 命令 设置断电方式
|
||||
* 命令 设置通断电方式
|
||||
*/
|
||||
public static final String COMMAND_OUTAGE_WAY = "set";
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.ruoyi.common.core.domain.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
|
@ -186,4 +187,14 @@ public class SmUser extends BaseEntity
|
|||
@Excel(name = "限制退款原因")
|
||||
@ApiModelProperty("限制退款原因")
|
||||
private String limitRefundReason;
|
||||
|
||||
@Excel(name = "风险提现次数")
|
||||
@ApiModelProperty("风险提现次数")
|
||||
private Integer riskCount;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Excel(name = "限制提现时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty("限制提现时间")
|
||||
private LocalDateTime limitWithdrawTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.text.ParseException;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
@ -316,4 +317,12 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
|
|||
public static LocalDateTime toLocalDate(String timeStr, String format) {
|
||||
return LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern(format));
|
||||
}
|
||||
|
||||
public static LocalDateTime toLocalDateTime(Date date) {
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
}
|
||||
|
||||
public static String format(LocalDateTime time, String format) {
|
||||
return time.format(DateTimeFormatter.ofPattern(format));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,10 @@ public enum ConfigKey {
|
|||
DAILY_WITHDRAW_COUNT("daily.withdraw.count", "单日单用户提现次数(次)"),
|
||||
NOVERIFY_WITHDRAW_SINGLE("noverify.withdraw.single", "提现单笔免审核额度(元)"),
|
||||
RECHARGE_MIN_SERVICE("recharge.min.service","充值最低服务费(元)"),
|
||||
ORDER_AUTO_CLOSE_CD("order.auto.close.cd", "订单自动关闭冷却时间(分)");
|
||||
ORDER_AUTO_CLOSE_CD("order.auto.close.cd", "订单自动关闭冷却时间(分)"),
|
||||
RISK_WITHDRAW_TIME("risk.withdraw.time", "风控订单和提现相隔时长(分钟)"),
|
||||
RISK_WITHDRAW_COUNT("risk.withdraw.count", "累计风险次数"),
|
||||
RISK_WITHDRAW_ENABLED("risk.withdraw.enabled", "是否开启提现风控");
|
||||
|
||||
private final String key;
|
||||
private final String msg;
|
||||
|
|
|
@ -14,8 +14,8 @@ import java.util.Objects;
|
|||
@Getter
|
||||
public enum DeviceOutageWay {
|
||||
|
||||
NOT_OUTAGE("0", "到时不断电"),
|
||||
IMMEDIATE("1", "到时立即断电");
|
||||
NOT_OUTAGE("0", "正"),
|
||||
IMMEDIATE("1", "反");
|
||||
|
||||
private final String value;
|
||||
private final String msg;
|
||||
|
|
|
@ -299,4 +299,9 @@ public interface TransactionBillService
|
|||
* 开启/关闭订单设备
|
||||
*/
|
||||
int switchDevice(TransactionBillVO bill, boolean open);
|
||||
|
||||
/**
|
||||
* 查询最后一个
|
||||
*/
|
||||
TransactionBillVO selectLastOne(TransactionBillQuery query);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.ruoyi.ss.transactionBill.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.ValidateResult;
|
||||
import com.ruoyi.ss.transactionBill.domain.bo.WithdrawBO;
|
||||
|
||||
/**
|
||||
* @author wjh
|
||||
* 2024/9/21
|
||||
*/
|
||||
public interface WithdrawValidator {
|
||||
|
||||
/**
|
||||
* 判断提现是否有风险
|
||||
*/
|
||||
boolean hasRisk(WithdrawBO bo);
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.ruoyi.ss.transactionBill.service.impl;
|
|||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.core.domain.ValidateResult;
|
||||
import com.ruoyi.common.core.redis.RedisLock;
|
||||
import com.ruoyi.common.core.redis.enums.RedisLockKey;
|
||||
import com.ruoyi.common.enums.WithdrawServiceType;
|
||||
|
@ -50,6 +51,7 @@ import com.ruoyi.ss.transactionBill.mapper.TransactionBillMapper;
|
|||
import com.ruoyi.ss.transactionBill.service.TransactionBillConverter;
|
||||
import com.ruoyi.ss.transactionBill.service.TransactionBillService;
|
||||
import com.ruoyi.ss.transactionBill.service.TransactionBillValidator;
|
||||
import com.ruoyi.ss.transactionBill.service.WithdrawValidator;
|
||||
import com.ruoyi.ss.transfer.domain.TransferVO;
|
||||
import com.ruoyi.ss.transfer.interfaces.AfterTransfer;
|
||||
import com.ruoyi.ss.transfer.service.TransferConverter;
|
||||
|
@ -155,6 +157,9 @@ public class TransactionBillServiceImpl implements TransactionBillService, After
|
|||
@Autowired
|
||||
private TransactionBillConverter transactionBillConverter;
|
||||
|
||||
@Autowired
|
||||
private WithdrawValidator withdrawValidator;
|
||||
|
||||
/**
|
||||
* 查询充值记录
|
||||
*
|
||||
|
@ -414,7 +419,35 @@ public class TransactionBillServiceImpl implements TransactionBillService, After
|
|||
|
||||
ServiceUtil.assertion(user == null, "用户不存在");
|
||||
ServiceUtil.assertion(user.getIsReal() == null || !user.getIsReal(), "用户未实名认证,无法提现");
|
||||
ServiceUtil.assertion(user.getLimitWithdraw() != null && user.getLimitWithdraw(), "您被限制提现:" + user.getLimitWithdrawReason());
|
||||
|
||||
// 判断用户是否被限制提现
|
||||
boolean limitWithdraw = user.getLimitWithdraw() != null && user.getLimitWithdraw();
|
||||
if (limitWithdraw) {
|
||||
LocalDateTime limitWithdrawTime = user.getLimitWithdrawTime();
|
||||
if (limitWithdrawTime == null) {
|
||||
throw new ServiceException("您被永久限制提现:" + user.getLimitWithdrawReason());
|
||||
} else {
|
||||
throw new ServiceException("您被限制提现至" + DateUtils.format(limitWithdrawTime, DateUtils.YYYY_MM_DD_HH_MM_SS) + ":" + user.getLimitWithdrawReason() );
|
||||
}
|
||||
}
|
||||
|
||||
// 风控规则,判断用户是否有风险
|
||||
boolean enabled = sysConfigService.getBoolean(ConfigKey.RISK_WITHDRAW_ENABLED);
|
||||
if (enabled) {
|
||||
boolean hasRisk = withdrawValidator.hasRisk(bo);
|
||||
if (hasRisk) {
|
||||
// 累计一次风险次数
|
||||
userService.addRiskCount(userId, 1);
|
||||
// 若用户风险次数已达到阈值,将用户标记为提现风险
|
||||
int riskWithdrawCount = sysConfigService.getInt(ConfigKey.RISK_WITHDRAW_COUNT);
|
||||
if (user.getRiskCount() + 1 >= riskWithdrawCount) {
|
||||
userService.limitWithdraw(userId, LocalDateTime.now().plusDays(1), "根据风控规则判断,您的提现具有风险", "风险客户");
|
||||
// 返回错误
|
||||
throw new ServiceException("提现具有风险,无法提现");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 判断今天提现成功和正在审核中的提现是否超过限额
|
||||
String dailyLimitStr = sysConfigService.selectConfigByKey(ConfigKey.DAILY_WITHDRAW_AMOUNT.getKey());
|
||||
|
@ -1649,6 +1682,12 @@ public class TransactionBillServiceImpl implements TransactionBillService, After
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionBillVO selectLastOne(TransactionBillQuery query) {
|
||||
PageHelper.orderBy("stb.bill_id desc");
|
||||
return selectOne(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserWithdrawServiceVO getUserWithdrawService(Long userId, Long channelId) {
|
||||
SmUserVo user = userService.selectSmUserByUserId(userId);
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package com.ruoyi.ss.transactionBill.service.impl;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseValidator;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.ss.transactionBill.domain.TransactionBillQuery;
|
||||
import com.ruoyi.ss.transactionBill.domain.bo.WithdrawBO;
|
||||
import com.ruoyi.ss.transactionBill.domain.dto.WithdrawDTO;
|
||||
import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillStatus;
|
||||
import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillType;
|
||||
import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO;
|
||||
import com.ruoyi.ss.transactionBill.service.TransactionBillService;
|
||||
import com.ruoyi.ss.transactionBill.service.WithdrawValidator;
|
||||
import com.ruoyi.ss.user.domain.SmUserVo;
|
||||
import com.ruoyi.system.domain.enums.config.ConfigKey;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author wjh
|
||||
* 2024/9/21
|
||||
*/
|
||||
@Service
|
||||
public class WithdrawValidatorImpl extends BaseValidator implements WithdrawValidator {
|
||||
|
||||
@Autowired
|
||||
private TransactionBillService transactionBillService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 判断提现是否有风险
|
||||
*
|
||||
* @param bo
|
||||
*/
|
||||
@Override
|
||||
public boolean hasRisk(WithdrawBO bo) {
|
||||
if (bo == null || bo.getUser() == null || bo.getDto() == null) {
|
||||
return false;
|
||||
}
|
||||
SmUserVo user = bo.getUser();
|
||||
WithdrawDTO dto = bo.getDto();
|
||||
|
||||
// 查询最近一次的订单
|
||||
TransactionBillQuery query = new TransactionBillQuery();
|
||||
query.setUserId(user.getUserId());
|
||||
query.setType(TransactionBillType.RECHARGE.getType());
|
||||
query.setStatus(TransactionBillStatus.SUCCESS.getStatus());
|
||||
TransactionBillVO recharge = transactionBillService.selectLastOne(query);
|
||||
if (recharge == null || recharge.getPayTime() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断订单金额是否和提现一致、订单时间与提现时间是否相差较近
|
||||
// 金额一致
|
||||
boolean equalsMoney = recharge.getArrivalAmount() != null && dto.getMoney() != null && recharge.getArrivalAmount().compareTo(dto.getMoney()) == 0;
|
||||
// 提现时间相近
|
||||
int riskWithdrawTime = sysConfigService.getInt(ConfigKey.RISK_WITHDRAW_TIME); // 最低允许相隔时长(分钟)
|
||||
Duration between = Duration.between(DateUtils.toLocalDateTime(recharge.getPayTime()), LocalDateTime.now());
|
||||
boolean timeLimit = between.toMinutes() < riskWithdrawTime;
|
||||
if (equalsMoney && timeLimit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -109,4 +109,12 @@ public interface SmUserMapper
|
|||
* 查询用户余额
|
||||
*/
|
||||
BigDecimal selectSumOfBalance(SmUserQuery query);
|
||||
|
||||
/**
|
||||
* 增加一次风险次数
|
||||
* @param userId
|
||||
* @param count
|
||||
* @return
|
||||
*/
|
||||
int addRiskCount(@Param("userId") Long userId,@Param("count") int count);
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
su.limit_withdraw_reason,
|
||||
su.limit_refund,
|
||||
su.limit_refund_reason,
|
||||
su.risk_count,
|
||||
su.limit_withdraw_time,
|
||||
(select sum(stb.money) from sm_transaction_bill stb where stb.user_id = su.user_id and stb.type = '1' and stb.status = '2') as recharge_amount,
|
||||
(select sum(stb.arrival_amount) from sm_transaction_bill stb where stb.user_id = su.user_id and stb.type = '2' and stb.status = '14') as with_drawl_amount,
|
||||
(select sum(stb.arrival_amount) from sm_transaction_bill stb where stb.mch_id = su.user_id and stb.type = '1' and stb.status = '2') as total_income
|
||||
|
@ -169,6 +171,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="limitWithdrawReason != null">limit_withdraw_reason,</if>
|
||||
<if test="limitRefund != null">limit_refund,</if>
|
||||
<if test="limitRefundReason != null">limit_refund_reason,</if>
|
||||
<if test="riskCount != null">risk_count,</if>
|
||||
<if test="limitWithdrawTime != null">limit_withdraw_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="userName != null and userName != ''">#{userName},</if>
|
||||
|
@ -206,9 +210,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="limitWithdrawReason != null">#{limitWithdrawReason},</if>
|
||||
<if test="limitRefund != null">#{limitRefund},</if>
|
||||
<if test="limitRefundReason != null">#{limitRefundReason},</if>
|
||||
<if test="riskCount != null">#{riskCount},</if>
|
||||
<if test="limitWithdrawTime != null">#{limitWithdrawTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<insert id="addRiskCount">
|
||||
update sm_user
|
||||
set risk_count = risk_count + 1
|
||||
where user_id = #{userId} and del_flag = '0'
|
||||
</insert>
|
||||
|
||||
<update id="addBalance">
|
||||
update sm_user
|
||||
set balance = balance + #{amount}
|
||||
|
@ -253,6 +265,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="limitWithdrawReason != null">limit_withdraw_reason = #{limitWithdrawReason},</if>
|
||||
<if test="limitRefund != null">limit_refund = #{limitRefund},</if>
|
||||
<if test="limitRefundReason != null">limit_refund_reason = #{limitRefundReason},</if>
|
||||
<if test="riskCount != null">risk_count = #{riskCount},</if>
|
||||
<if test="limitWithdrawTime != null">limit_withdraw_time = #{limitWithdrawTime},</if>
|
||||
</trim>
|
||||
where user_id = #{userId}
|
||||
</update>
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.ruoyi.ss.user.domain.SmUserVo;
|
|||
import com.ruoyi.ss.user.domain.dto.UserRealNameDTO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -209,4 +210,19 @@ public interface ISmUserService
|
|||
* 实名认证
|
||||
*/
|
||||
int realName(UserRealNameDTO dto);
|
||||
|
||||
/**
|
||||
* 限制提现
|
||||
* @param userId
|
||||
* @param limitTime
|
||||
* @param reason
|
||||
* @param remark
|
||||
* @return
|
||||
*/
|
||||
int limitWithdraw(Long userId, LocalDateTime limitTime, String reason, String remark);
|
||||
|
||||
/**
|
||||
* 添加一次风险次数
|
||||
*/
|
||||
int addRiskCount(Long userId, int count);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -297,6 +298,25 @@ public class SmUserServiceImpl implements ISmUserService
|
|||
return smUserMapper.updateSmUser(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int limitWithdraw(Long userId, LocalDateTime limitTime, String reason, String remark) {
|
||||
if (userId == null) {
|
||||
return 0;
|
||||
}
|
||||
SmUser data = new SmUser();
|
||||
data.setUserId(userId);
|
||||
data.setLimitWithdraw(true);
|
||||
data.setLimitWithdrawTime(limitTime);
|
||||
data.setLimitWithdrawReason(reason);
|
||||
data.setRemark(remark);
|
||||
return smUserMapper.updateSmUser(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addRiskCount(Long userId, int count) {
|
||||
return smUserMapper.addRiskCount(userId, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑删除前校验
|
||||
* @param userIds
|
||||
|
|
Loading…
Reference in New Issue
Block a user