From 534574eb712b919dd8d41ec28f3b1363fa21d33b Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 10 Jul 2026 20:41:44 +0800 Subject: [PATCH] fix(order): prevent V5Pay prefix collisions --- .../circle/order/app/common/V5PayOrderNoUtils.java | 11 ++++++----- .../circle/order/app/common/V5PaySignUtilsTest.java | 13 +++++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/V5PayOrderNoUtils.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/V5PayOrderNoUtils.java index 1dbe20a7..2dd169b4 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/V5PayOrderNoUtils.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/V5PayOrderNoUtils.java @@ -8,7 +8,8 @@ import java.util.regex.Pattern; public final class V5PayOrderNoUtils { private static final String DEFAULT_PREFIX = "ASLAN"; - private static final Pattern PREFIX_PATTERN = Pattern.compile("[A-Za-z0-9]+"); + private static final Pattern PREFIX_PATTERN = Pattern.compile("[A-Za-z]+"); + private static final Pattern PROVIDER_ORDER_NO_PATTERN = Pattern.compile("[A-Za-z0-9]+"); private static final Pattern LOCAL_ORDER_NO_PATTERN = Pattern.compile("[1-9][0-9]{0,18}"); private V5PayOrderNoUtils() { @@ -28,13 +29,13 @@ public final class V5PayOrderNoUtils { public static String localOrderNo(String prefix, String providerOrderNo) { String expected = normalizedPrefix(prefix); String value = Objects.toString(providerOrderNo, "").trim(); - if (!PREFIX_PATTERN.matcher(value).matches() + if (!PROVIDER_ORDER_NO_PATTERN.matcher(value).matches() || !value.startsWith(expected) || value.length() == expected.length()) { return ""; } String localOrderNo = value.substring(expected.length()); - // 回调只接受当前前缀后紧跟正数雪花 ID;这同时拒绝其他系统前缀、分隔符和前缀碰撞后的字母尾缀。 + // 纯字母命名空间后只能紧跟正数雪花 ID;禁止数字前缀才能避免 ASLAN+123 与 ASLAN1+23 产生碰撞。 return isValidLocalOrderNo(localOrderNo) ? localOrderNo : ""; } @@ -42,8 +43,8 @@ public final class V5PayOrderNoUtils { String value = Objects.toString(prefix, DEFAULT_PREFIX).trim(); String normalized = (value.isBlank() ? DEFAULT_PREFIX : value).toUpperCase(Locale.ROOT); if (!PREFIX_PATTERN.matcher(normalized).matches()) { - // 共享商户依赖前缀隔离,配置中出现分隔符或非 ASCII 字符时必须快速失败,不能静默改写。 - throw new IllegalArgumentException("V5Pay order prefix must contain only ASCII letters and digits"); + // 无分隔订单号若允许数字前缀会产生命名空间碰撞;符号和 Unicode 同样不符合支付方格式。 + throw new IllegalArgumentException("V5Pay order prefix must contain only ASCII letters"); } return normalized; } diff --git a/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/common/V5PaySignUtilsTest.java b/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/common/V5PaySignUtilsTest.java index 54e5a398..49a27ec8 100644 --- a/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/common/V5PaySignUtilsTest.java +++ b/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/common/V5PaySignUtilsTest.java @@ -34,13 +34,20 @@ public class V5PaySignUtilsTest { } @Test - public void orderPrefixNormalizesToUppercaseAlphanumericNamespace() { + public void orderPrefixNormalizesToUppercaseAsciiLetterNamespace() { Assert.assertEquals("ASLAN123", V5PayOrderNoUtils.providerOrderNo("aslan", "123")); - Assert.assertEquals("A5SLAN42", V5PayOrderNoUtils.providerOrderNo("a5slan", "42")); Assert.assertEquals("ASLAN123", V5PayOrderNoUtils.providerOrderNo("", "123")); Assert.assertEquals("123", V5PayOrderNoUtils.localOrderNo("aslan", "ASLAN123")); } + @Test + public void numericPrefixIsRejectedToPreventNamespaceCollision() { + Assert.assertEquals("ASLAN123", V5PayOrderNoUtils.providerOrderNo("ASLAN", "123")); + // ASLAN1 + 23 会与 ASLAN + 123 形成相同字符串,因此共享商户命名空间不能允许数字前缀。 + assertInvalidOrderNo("ASLAN1", "23"); + assertInvalidOrderNo("1ASLAN", "23"); + } + @Test public void orderParserRejectsOtherNamespaceEmptyAndIllegalCharacters() { Assert.assertEquals("", V5PayOrderNoUtils.localOrderNo("ASLAN", "LALU123")); @@ -66,6 +73,8 @@ public class V5PaySignUtilsTest { assertInvalidOrderNo("ASLAN-", "123"); assertInvalidOrderNo("AS LAN", "123"); + assertInvalidOrderNo("A5SLAN", "123"); + assertInvalidOrderNo("阿斯兰", "123"); assertInvalidOrderNo("ASLAN", ""); assertInvalidOrderNo("ASLAN", "1-23"); assertInvalidOrderNo("ASLAN", "9223372036854775808");