mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 15:35:38 +08:00
feat(risk): 接入海外IP地域拦截风控维度, 复用ip2region判定境外IP, 新增overseas_ip命中类型, 受风控总开关门控
This commit is contained in:
@@ -57,4 +57,10 @@ public class PayRiskCheckContext {
|
||||
|
||||
/// 事前命中是否阻断下单(null/true=阻断;false=仅落命中记录)
|
||||
private Boolean blockOnHit;
|
||||
|
||||
/// 是否拦截海外 IP(null/false=不拦截;true=拦截)
|
||||
///
|
||||
/// 地域策略开关快照, 由 [cn.daxpay.open.payment.trade.runtime.service.pay.common.PayRiskAssistService]
|
||||
/// 读取平台配置后注入, 供检查器按 IP 归属地判定是否命中。
|
||||
private Boolean blockOverseasIp;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public class PayRiskAssistService {
|
||||
PayRiskCheckContext ctx = buildContextFromParam(payParam, scene);
|
||||
// false=仅记录不拦截;缺省/true=命中拒绝下单
|
||||
ctx.setBlockOnHit(!Boolean.FALSE.equals(config.getRiskBlockBeforePay()));
|
||||
// 海外 IP 拦截开关(地域策略)
|
||||
ctx.setBlockOverseasIp(config.getBlockOverseasIp());
|
||||
checker.checkBeforePay(ctx);
|
||||
}
|
||||
|
||||
@@ -68,6 +70,8 @@ public class PayRiskAssistService {
|
||||
return;
|
||||
}
|
||||
PayRiskCheckContext ctx = buildContextFromTrade(trade);
|
||||
// 海外 IP 拦截开关(地域策略, 事后仅记录海外访问)
|
||||
ctx.setBlockOverseasIp(config.getBlockOverseasIp());
|
||||
if (StrUtil.isNotBlank(buyerId)) {
|
||||
ctx.setBuyerId(buyerId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "Alipay user",
|
||||
"wechat_openid": "WeChat OpenId"
|
||||
"wechat_openid": "WeChat OpenId",
|
||||
"overseas_ip": "Overseas IP"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "Pengguna Alipay",
|
||||
"wechat_openid": "OpenId WeChat"
|
||||
"wechat_openid": "OpenId WeChat",
|
||||
"overseas_ip": "IP Luar Negeri"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "Alipay ユーザー",
|
||||
"wechat_openid": "WeChat OpenId"
|
||||
"wechat_openid": "WeChat OpenId",
|
||||
"overseas_ip": "海外IP"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "알리페이 사용자",
|
||||
"wechat_openid": "위챗 OpenId"
|
||||
"wechat_openid": "위챗 OpenId",
|
||||
"overseas_ip": "해외 IP"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "Pengguna Alipay",
|
||||
"wechat_openid": "OpenId WeChat"
|
||||
"wechat_openid": "OpenId WeChat",
|
||||
"overseas_ip": "IP Luar Negara"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "ผู้ใช้ Alipay",
|
||||
"wechat_openid": "OpenId WeChat"
|
||||
"wechat_openid": "OpenId WeChat",
|
||||
"overseas_ip": "IP ต่างประเทศ"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "Người dùng Alipay",
|
||||
"wechat_openid": "OpenId WeChat"
|
||||
"wechat_openid": "OpenId WeChat",
|
||||
"overseas_ip": "IP nước ngoài"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "支付宝用户",
|
||||
"wechat_openid": "微信 OpenId"
|
||||
"wechat_openid": "微信 OpenId",
|
||||
"overseas_ip": "海外IP"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "支付寶用戶",
|
||||
"wechat_openid": "微信 OpenId"
|
||||
"wechat_openid": "微信 OpenId",
|
||||
"overseas_ip": "海外IP"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ip": "IP",
|
||||
"alipay_user": "支付寶用戶",
|
||||
"wechat_openid": "微信 OpenId"
|
||||
"wechat_openid": "微信 OpenId",
|
||||
"overseas_ip": "海外IP"
|
||||
}
|
||||
|
||||
@@ -11,4 +11,13 @@
|
||||
<artifactId>daxpay-plugin-risk</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<description>支付风控插件(黑名单配置 + 命中记录)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 审计日志模块(IP 归属地查询服务, 海外 IP 拦截依赖) -->
|
||||
<dependency>
|
||||
<groupId>cn.daxpay.open</groupId>
|
||||
<artifactId>capability-audit-log</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -18,7 +18,9 @@ public enum PayBlacklistTypeEnum implements I18nSupport {
|
||||
/// 支付宝用户(userId,通道内全局)
|
||||
ALIPAY_USER("alipay_user"),
|
||||
/// 微信 OpenId(绑定平台支付应用)
|
||||
WECHAT_OPENID("wechat_openid");
|
||||
WECHAT_OPENID("wechat_openid"),
|
||||
/// 海外 IP(地域策略命中, 非黑名单来源)
|
||||
OVERSEAS_IP("overseas_ip");
|
||||
|
||||
private final String code;
|
||||
|
||||
|
||||
@@ -166,6 +166,10 @@ public class PayBlacklistService {
|
||||
|
||||
/// 将命中快照映射为名单 type + wxAppId;微信缺 AppId 则返回 null
|
||||
private ResolvedIdentity resolveIdentity(String hitType, String channel, String wxAppId) {
|
||||
// 海外 IP 命中加黑: 映射为普通 IP 黑名单(持续拉黑该具体 IP)
|
||||
if (PayBlacklistTypeEnum.OVERSEAS_IP.getCode().equals(hitType)) {
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.IP.getCode(), null);
|
||||
}
|
||||
if (PayBlacklistTypeEnum.IP.getCode().equals(hitType)) {
|
||||
return new ResolvedIdentity(PayBlacklistTypeEnum.IP.getCode(), null);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import cn.daxpay.open.payment.strategy.risk.PayRiskCheckContext;
|
||||
import cn.daxpay.open.payment.strategy.risk.PayRiskChecker;
|
||||
import cn.daxpay.open.platform.core.code.PayErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelEnum;
|
||||
import cn.daxpay.open.platform.capability.audit.log.service.ip2region.IpRegion;
|
||||
import cn.daxpay.open.platform.capability.audit.log.service.ip2region.IpToRegionService;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.plugin.risk.entity.PayBlacklist;
|
||||
import cn.daxpay.open.plugin.risk.enums.PayBlacklistTypeEnum;
|
||||
@@ -31,6 +33,7 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
|
||||
private final PayBlacklistService payBlacklistService;
|
||||
private final PayRiskHitService payRiskHitService;
|
||||
private final IpToRegionService ipToRegionService;
|
||||
|
||||
/// 用户标识黑名单存在性缓存(短 TTL 30s)
|
||||
///
|
||||
@@ -60,6 +63,10 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
+ "tradeType={}, method={}, mchNo={}, clientIp={}",
|
||||
ctx.getTradeType(), ctx.getMethod(), ctx.getMchNo(), ctx.getClientIp());
|
||||
}
|
||||
// 海外 IP 拦截(地域策略, 非黑名单)
|
||||
if (Boolean.TRUE.equals(ctx.getBlockOverseasIp())) {
|
||||
checkOverseasIp(ctx, throwOnHit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,6 +82,10 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
if (StrUtil.isNotBlank(ctx.getBuyerId()) && !StrUtil.equals(ctx.getBuyerId(), ctx.getOpenId())) {
|
||||
checkUserIdentity(ctx, ctx.getBuyerId(), false);
|
||||
}
|
||||
// 海外 IP 访问记录(事后仅记录, 不阻断)
|
||||
if (Boolean.TRUE.equals(ctx.getBlockOverseasIp())) {
|
||||
checkOverseasIp(ctx, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,4 +138,29 @@ public class DefaultPayRiskChecker implements PayRiskChecker {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 海外 IP 地域拦截(country≠中国, 港澳台放行; 未知/内网 fail-open)
|
||||
///
|
||||
/// 与黑名单不同, 海外命中不关联名单行(blacklistId=null), 命中类型为 [PayBlacklistTypeEnum#OVERSEAS_IP]。
|
||||
private void checkOverseasIp(PayRiskCheckContext ctx, boolean throwOnHit) {
|
||||
String ip = ctx.getClientIp();
|
||||
if (StrUtil.isBlank(ip)) {
|
||||
return;
|
||||
}
|
||||
IpRegion region = ipToRegionService.getRegionByIp(ip);
|
||||
// 未知(IPv6/查询失败)/ 内网 / 国内(含港澳台) → 放行
|
||||
if (region == null || region.isInnerIp() || region.isChinaIp()) {
|
||||
return;
|
||||
}
|
||||
// 海外 → 命中记录(blacklistId=null, 非黑名单来源)
|
||||
try {
|
||||
payRiskHitService.recordHit(ctx, PayBlacklistTypeEnum.OVERSEAS_IP.getCode(), ip, null);
|
||||
} catch (Exception e) {
|
||||
log.warn("记录海外IP命中失败 ip={}: {}", ip, e.getMessage());
|
||||
}
|
||||
if (throwOnHit) {
|
||||
// 交易被限制(模糊文案,防探测)
|
||||
throw new BizInfoException(PayErrorCode.OPERATION_FAIL, "pay.error.risk.blacklist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user