diff --git a/server-rs/crates/module-runtime/src/domain.rs b/server-rs/crates/module-runtime/src/domain.rs index 44148900c..9d60e5cef 100644 --- a/server-rs/crates/module-runtime/src/domain.rs +++ b/server-rs/crates/module-runtime/src/domain.rs @@ -1255,6 +1255,131 @@ impl RuntimeProfileMembershipTier { } } +/// 会员档位身份。取代旧的 `RuntimeProfileMembershipTier`,作为唯一权威档位标识。 +/// +/// 变体顺序即排序基准(`Normal < Starter < Plus < Pro < Max`),升级比较不再依赖旧枚举别名。 +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum RuntimeProfileMembershipPlan { + Normal, + Starter, + Plus, + Pro, + Max, +} + +impl RuntimeProfileMembershipPlan { + pub fn as_str(&self) -> &'static str { + match self { + Self::Normal => "normal", + Self::Starter => "starter", + Self::Plus => "plus", + Self::Pro => "pro", + Self::Max => "max", + } + } + + /// 严格解析对外 wire token;未知 token 返回 `None`,调用方必须显式拒绝,不接受自由字符串。 + pub fn parse(value: &str) -> Option { + match value { + "normal" => Some(Self::Normal), + "starter" => Some(Self::Starter), + "plus" => Some(Self::Plus), + "pro" => Some(Self::Pro), + "max" => Some(Self::Max), + _ => None, + } + } + + /// `Normal` 不是可售档位,其余档位可在充值中心购买。 + pub fn is_purchasable(&self) -> bool { + !matches!(self, Self::Normal) + } +} + +/// 会员购买周期类型。它只决定本次购买的总期数与到期日,单期窗口一律按自然月推进。 +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum RuntimeProfileMembershipCycleKind { + Monthly, + Yearly, +} + +impl RuntimeProfileMembershipCycleKind { + pub fn as_str(&self) -> &'static str { + match self { + Self::Monthly => "monthly", + Self::Yearly => "yearly", + } + } + + pub fn parse(value: &str) -> Option { + match value { + "monthly" => Some(Self::Monthly), + "yearly" => Some(Self::Yearly), + _ => None, + } + } + + /// 本次购买的总期数:月付 1 期、年付 12 期(每期仍是一个自然月)。 + pub fn cycle_count(&self) -> u32 { + match self { + Self::Monthly => 1, + Self::Yearly => 12, + } + } +} + +/// 档位允许选择的模型范围。本期只落字段、只用于展示,服务端不做拦截。 +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum RuntimeProfileMembershipModelAccess { + Basic, + Full, +} + +impl RuntimeProfileMembershipModelAccess { + pub fn as_str(&self) -> &'static str { + match self { + Self::Basic => "basic", + Self::Full => "full", + } + } + + pub fn parse(value: &str) -> Option { + match value { + "basic" => Some(Self::Basic), + "full" => Some(Self::Full), + _ => None, + } + } +} + +/// 会员订单产生的权益变更类型,用于订单快照区分「新购」与「补差升级」。 +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum RuntimeProfileMembershipChangeKind { + Purchase, + Upgrade, +} + +impl RuntimeProfileMembershipChangeKind { + pub fn as_str(&self) -> &'static str { + match self { + Self::Purchase => "purchase", + Self::Upgrade => "upgrade", + } + } + + pub fn parse(value: &str) -> Option { + match value { + "purchase" => Some(Self::Purchase), + "upgrade" => Some(Self::Upgrade), + _ => None, + } + } +} + #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum RuntimeProfileRechargeOrderStatus { diff --git a/server-rs/crates/module-runtime/tests/membership_plan_enum.rs b/server-rs/crates/module-runtime/tests/membership_plan_enum.rs new file mode 100644 index 000000000..fb3cdfda1 --- /dev/null +++ b/server-rs/crates/module-runtime/tests/membership_plan_enum.rs @@ -0,0 +1,81 @@ +use module_runtime::{ + RuntimeProfileMembershipChangeKind, RuntimeProfileMembershipCycleKind, + RuntimeProfileMembershipModelAccess, RuntimeProfileMembershipPlan, +}; + +#[test] +fn membership_plan_tokens_round_trip_strictly() { + // 中文注释:对外 wire token 必须与枚举一一对应,未知 token 一律解析失败。 + let cases = [ + (RuntimeProfileMembershipPlan::Normal, "normal"), + (RuntimeProfileMembershipPlan::Starter, "starter"), + (RuntimeProfileMembershipPlan::Plus, "plus"), + (RuntimeProfileMembershipPlan::Pro, "pro"), + (RuntimeProfileMembershipPlan::Max, "max"), + ]; + for (plan, token) in cases { + assert_eq!(plan.as_str(), token); + assert_eq!(RuntimeProfileMembershipPlan::parse(token), Some(plan)); + } + + assert_eq!(RuntimeProfileMembershipPlan::parse("basic"), None); + assert_eq!(RuntimeProfileMembershipPlan::parse("ultimate"), None); + assert_eq!(RuntimeProfileMembershipPlan::parse("PLUS"), None); + assert_eq!(RuntimeProfileMembershipPlan::parse(""), None); +} + +#[test] +fn membership_plan_normal_is_not_purchasable() { + // 中文注释:Normal 是非会员占位档位,不允许被购买。 + assert!(!RuntimeProfileMembershipPlan::Normal.is_purchasable()); + for plan in [ + RuntimeProfileMembershipPlan::Starter, + RuntimeProfileMembershipPlan::Plus, + RuntimeProfileMembershipPlan::Pro, + RuntimeProfileMembershipPlan::Max, + ] { + assert!(plan.is_purchasable()); + } +} + +#[test] +fn membership_cycle_kind_maps_to_period_count() { + // 中文注释:年付 12 期、月付 1 期,每期仍是一个自然月。 + assert_eq!(RuntimeProfileMembershipCycleKind::Monthly.cycle_count(), 1); + assert_eq!(RuntimeProfileMembershipCycleKind::Yearly.cycle_count(), 12); + assert_eq!( + RuntimeProfileMembershipCycleKind::Monthly.as_str(), + "monthly" + ); + assert_eq!(RuntimeProfileMembershipCycleKind::Yearly.as_str(), "yearly"); + assert_eq!( + RuntimeProfileMembershipCycleKind::parse("yearly"), + Some(RuntimeProfileMembershipCycleKind::Yearly) + ); + assert_eq!(RuntimeProfileMembershipCycleKind::parse("weekly"), None); +} + +#[test] +fn membership_model_access_and_change_kind_tokens_round_trip() { + assert_eq!(RuntimeProfileMembershipModelAccess::Basic.as_str(), "basic"); + assert_eq!(RuntimeProfileMembershipModelAccess::Full.as_str(), "full"); + assert_eq!( + RuntimeProfileMembershipModelAccess::parse("full"), + Some(RuntimeProfileMembershipModelAccess::Full) + ); + assert_eq!(RuntimeProfileMembershipModelAccess::parse("premium"), None); + + assert_eq!( + RuntimeProfileMembershipChangeKind::Purchase.as_str(), + "purchase" + ); + assert_eq!( + RuntimeProfileMembershipChangeKind::Upgrade.as_str(), + "upgrade" + ); + assert_eq!( + RuntimeProfileMembershipChangeKind::parse("upgrade"), + Some(RuntimeProfileMembershipChangeKind::Upgrade) + ); + assert_eq!(RuntimeProfileMembershipChangeKind::parse("downgrade"), None); +}