diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/risk/PayRiskCheckContext.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/risk/PayRiskCheckContext.java index 35e2f33d9..10f2b32cf 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/risk/PayRiskCheckContext.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/risk/PayRiskCheckContext.java @@ -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; } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/common/PayRiskAssistService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/common/PayRiskAssistService.java index 626868fdf..13118bba3 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/common/PayRiskAssistService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/common/PayRiskAssistService.java @@ -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); } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_blacklist_type.json index 55767a83a..a9fb43a74 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "Alipay user", - "wechat_openid": "WeChat OpenId" + "wechat_openid": "WeChat OpenId", + "overseas_ip": "Overseas IP" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/enum/pay_blacklist_type.json index ad4167299..8d3541b7c 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "Pengguna Alipay", - "wechat_openid": "OpenId WeChat" + "wechat_openid": "OpenId WeChat", + "overseas_ip": "IP Luar Negeri" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/enum/pay_blacklist_type.json index 9694d3917..ccbd93a73 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "Alipay ユーザー", - "wechat_openid": "WeChat OpenId" + "wechat_openid": "WeChat OpenId", + "overseas_ip": "海外IP" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/enum/pay_blacklist_type.json index d7716e9c3..ebc344e51 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "알리페이 사용자", - "wechat_openid": "위챗 OpenId" + "wechat_openid": "위챗 OpenId", + "overseas_ip": "해외 IP" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/enum/pay_blacklist_type.json index ad4167299..84f1b9630 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "Pengguna Alipay", - "wechat_openid": "OpenId WeChat" + "wechat_openid": "OpenId WeChat", + "overseas_ip": "IP Luar Negara" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/enum/pay_blacklist_type.json index 8bef83a47..573fbf587 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "ผู้ใช้ Alipay", - "wechat_openid": "OpenId WeChat" + "wechat_openid": "OpenId WeChat", + "overseas_ip": "IP ต่างประเทศ" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/enum/pay_blacklist_type.json index 4959ef2c6..8acc59747 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/enum/pay_blacklist_type.json @@ -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" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_blacklist_type.json index 38e0a115d..7d0172269 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "支付宝用户", - "wechat_openid": "微信 OpenId" + "wechat_openid": "微信 OpenId", + "overseas_ip": "海外IP" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/enum/pay_blacklist_type.json index d01e26988..08c48b60b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "支付寶用戶", - "wechat_openid": "微信 OpenId" + "wechat_openid": "微信 OpenId", + "overseas_ip": "海外IP" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/enum/pay_blacklist_type.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/enum/pay_blacklist_type.json index d01e26988..08c48b60b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/enum/pay_blacklist_type.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/enum/pay_blacklist_type.json @@ -1,5 +1,6 @@ { "ip": "IP", "alipay_user": "支付寶用戶", - "wechat_openid": "微信 OpenId" + "wechat_openid": "微信 OpenId", + "overseas_ip": "海外IP" } diff --git a/daxpay-plugin/daxpay-plugin-risk/pom.xml b/daxpay-plugin/daxpay-plugin-risk/pom.xml index 65bb04b31..8b2fafc1d 100644 --- a/daxpay-plugin/daxpay-plugin-risk/pom.xml +++ b/daxpay-plugin/daxpay-plugin-risk/pom.xml @@ -11,4 +11,13 @@ daxpay-plugin-risk jar 支付风控插件(黑名单配置 + 命中记录) + + + + + cn.daxpay.open + capability-audit-log + ${project.version} + + diff --git a/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/enums/PayBlacklistTypeEnum.java b/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/enums/PayBlacklistTypeEnum.java index d4d2f74b0..65edaf1de 100644 --- a/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/enums/PayBlacklistTypeEnum.java +++ b/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/enums/PayBlacklistTypeEnum.java @@ -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; diff --git a/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/service/PayBlacklistService.java b/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/service/PayBlacklistService.java index 142b00e33..985b65a79 100644 --- a/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/service/PayBlacklistService.java +++ b/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/service/PayBlacklistService.java @@ -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); } diff --git a/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/strategy/DefaultPayRiskChecker.java b/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/strategy/DefaultPayRiskChecker.java index 0529c8995..079b51d16 100644 --- a/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/strategy/DefaultPayRiskChecker.java +++ b/daxpay-plugin/daxpay-plugin-risk/src/main/java/cn/daxpay/open/plugin/risk/strategy/DefaultPayRiskChecker.java @@ -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"); + } + } }