fix(order): prevent V5Pay prefix collisions

This commit is contained in:
zhx 2026-07-10 20:41:44 +08:00
parent 0a4ca3df58
commit 534574eb71
2 changed files with 17 additions and 7 deletions

View File

@ -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;
}

View File

@ -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");