diff --git a/server-rs/crates/module-runtime/src/membership/upgrade.rs b/server-rs/crates/module-runtime/src/membership/upgrade.rs index 169f3213e..05f9690e6 100644 --- a/server-rs/crates/module-runtime/src/membership/upgrade.rs +++ b/server-rs/crates/module-runtime/src/membership/upgrade.rs @@ -143,6 +143,9 @@ pub fn check_runtime_profile_membership_upgrade_allowed( /// 期号是同一期账期的两个口径,账期已刷新仍按旧 `cycle_index` 报价会多收整整一个完整月 /// (年付还会把补点算成 0)。生产入口先用 `project_profile_membership_cycle_at` 把会员行 /// 投射到当前账期,因此这里拒绝的只会是「拿过期账期直接报价」的错误调用方。 +/// +/// 到期时刻不能早于本期刷新时刻(`expires_at >= cycle_resets_at`):账期刷新的前提是会员仍在 +/// 有效期内,否则报价会回显一个早于下次刷新日的到期时间,得到自相矛盾的结果。 fn validate_runtime_profile_membership_upgrade_quote_input( input: &RuntimeProfileMembershipUpgradeQuoteInput, ) -> Result<(), RuntimeProfileMembershipUpgradeRejection> { @@ -151,7 +154,9 @@ fn validate_runtime_profile_membership_upgrade_quote_input( let window_out_of_order = input.cycle_resets_at_micros <= input.cycle_started_at_micros; let now_outside_window = input.now_micros < input.cycle_started_at_micros || input.now_micros >= input.cycle_resets_at_micros; - if cycle_index_out_of_range || window_out_of_order || now_outside_window { + let expiry_out_of_order = input.expires_at_micros < input.cycle_resets_at_micros; + if cycle_index_out_of_range || window_out_of_order || now_outside_window || expiry_out_of_order + { return Err(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput); } Ok(()) @@ -453,6 +458,14 @@ mod tests { }), Err(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput) ); + // 中文注释:到期时刻早于本期刷新时刻属于自相矛盾的入参,必须显式拒绝。 + assert_eq!( + quote_runtime_profile_membership_upgrade(RuntimeProfileMembershipUpgradeQuoteInput { + expires_at_micros: beijing(2026, 7, 1, 9), + ..base_input() + }), + Err(RuntimeProfileMembershipUpgradeRejection::InvalidQuoteInput) + ); } #[test]