fix(会员升级): 相等价差 / 点差也拒绝,堵住 0 元升级

- quote_runtime_profile_membership_upgrade 先把 target.price_cents / period_points 与 current 比较,<= 直接返回 InvalidQuoteInput(评审第 15 条)
- 保留 rank 递增校验;旧实现用 checked_sub 只拦下溢,相等会算出 0 元升级 / 0 补点
- inverted_catalog_price_and_points_are_rejected 扩展相等分支,已验证去掉 <= 时该用例失败
This commit is contained in:
2026-10-04 19:41:09 +08:00
parent 54d1df9d21
commit cf2a8820d9
@@ -182,15 +182,13 @@ pub fn quote_runtime_profile_membership_upgrade(
input.now_micros,
);
// 目录价格 / 每期泥点必须严格随 rank 递增;倒挂时宁可报错,也不算出 0 元升级。
let price_delta_cents = target
.price_cents
.checked_sub(current.price_cents)
.ok_or(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput)?;
let points_delta = target
.period_points
.checked_sub(current.period_points)
.ok_or(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput)?;
// 目录价格 / 每期泥点必须严格随 rank 递增;相等或倒挂都直接拒绝,
// 否则 checked_sub 会把「相等」算成 0 元升级 / 0 补点,违背目录不变量。
if target.price_cents <= current.price_cents || target.period_points <= current.period_points {
return Err(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput);
}
let price_delta_cents = target.price_cents - current.price_cents;
let points_delta = target.period_points - current.period_points;
let (amount_cents, granted_points_delta) = match target.cycle_kind {
RuntimeProfileMembershipCycleKind::Monthly => (price_delta_cents, points_delta),
@@ -494,6 +492,19 @@ mod tests {
target.price_cents = current.price_cents + 1;
target.period_points = current.period_points - 1;
assert_eq!(
quote_runtime_profile_membership_upgrade(RuntimeProfileMembershipUpgradeQuoteInput {
current: current.clone(),
target: target.clone(),
..base_input()
}),
Err(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput)
);
// 中文注释:相等同样破坏「严格随 rank 递增」,必须拒绝;旧实现用 checked_sub
// 会把相等的价差 / 点差算成 0,从而产出 0 元升级 / 0 补点。
target.price_cents = current.price_cents;
target.period_points = current.period_points;
assert_eq!(
quote_runtime_profile_membership_upgrade(RuntimeProfileMembershipUpgradeQuoteInput {
current,