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 2dd169b4..cf64acfa 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 @@ -41,12 +41,12 @@ public final class V5PayOrderNoUtils { private static String normalizedPrefix(String prefix) { String value = Objects.toString(prefix, DEFAULT_PREFIX).trim(); - String normalized = (value.isBlank() ? DEFAULT_PREFIX : value).toUpperCase(Locale.ROOT); - if (!PREFIX_PATTERN.matcher(normalized).matches()) { - // 无分隔订单号若允许数字前缀会产生命名空间碰撞;符号和 Unicode 同样不符合支付方格式。 + String rawPrefix = value.isBlank() ? DEFAULT_PREFIX : value; + if (!PREFIX_PATTERN.matcher(rawPrefix).matches()) { + // 必须在大小写转换前校验原文;否则 ı/ß 等 Unicode 可能折叠成 ASCII 并与合法命名空间碰撞。 throw new IllegalArgumentException("V5Pay order prefix must contain only ASCII letters"); } - return normalized; + return rawPrefix.toUpperCase(Locale.ROOT); } private static boolean isValidLocalOrderNo(String localOrderNo) { 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 49a27ec8..83e6a5f9 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 @@ -48,6 +48,15 @@ public class V5PaySignUtilsTest { assertInvalidOrderNo("1ASLAN", "23"); } + @Test + public void unicodeCaseFoldingCannotAliasAsciiNamespace() { + Assert.assertEquals("ASLANI23", V5PayOrderNoUtils.providerOrderNo("aslani", "23")); + Assert.assertEquals("ASSLAN23", V5PayOrderNoUtils.providerOrderNo("asslan", "23")); + // Java 大写会将 dotless-i 与 sharp-s 折叠成 ASCII;原文校验必须在转换之前完成。 + assertInvalidOrderNo("aslanı", "23"); + assertInvalidOrderNo("aßlan", "23"); + } + @Test public void orderParserRejectsOtherNamespaceEmptyAndIllegalCharacters() { Assert.assertEquals("", V5PayOrderNoUtils.localOrderNo("ASLAN", "LALU123"));