From cf2a8820d9e15b00ca8f19667fb350a08aeb30a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sun, 4 Oct 2026 19:41:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E4=BC=9A=E5=91=98=E5=8D=87=E7=BA=A7):=20?= =?UTF-8?q?=E7=9B=B8=E7=AD=89=E4=BB=B7=E5=B7=AE=20/=20=E7=82=B9=E5=B7=AE?= =?UTF-8?q?=E4=B9=9F=E6=8B=92=E7=BB=9D=EF=BC=8C=E5=A0=B5=E4=BD=8F=200=20?= =?UTF-8?q?=E5=85=83=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 扩展相等分支,已验证去掉 <= 时该用例失败 --- .../module-runtime/src/membership/upgrade.rs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/server-rs/crates/module-runtime/src/membership/upgrade.rs b/server-rs/crates/module-runtime/src/membership/upgrade.rs index 051365bc5..64a9ae25a 100644 --- a/server-rs/crates/module-runtime/src/membership/upgrade.rs +++ b/server-rs/crates/module-runtime/src/membership/upgrade.rs @@ -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,