重构(计费): 余额接口并列暴露三池并去掉会员 tier 字段
- 余额响应改为每日免费 / 月度 / 永久三池并列,不再暴露总额与会员限时泥点 - 会员响应与充值中心以 plan 取代 tier,充值中心改为输出会员目录 - 微信虚拟支付按目录档位校验会员商品,不再依赖会员商品配置行 - 同步 shared-contracts 测试夹具到新的三池与 plan 口径 Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -35,14 +35,6 @@ pub const PROFILE_TASK_STATUS_CLAIMED: &str = "claimed";
|
||||
pub const PROFILE_TASK_STATUS_DISABLED: &str = "disabled";
|
||||
pub const PROFILE_RECHARGE_PRODUCT_KIND_POINTS: &str = "points";
|
||||
pub const PROFILE_RECHARGE_PRODUCT_KIND_MEMBERSHIP: &str = "membership";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_NORMAL: &str = "normal";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_MONTH: &str = "month";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_SEASON: &str = "season";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_YEAR: &str = "year";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_STARTER: &str = "starter";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_BASIC: &str = "basic";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_PRO: &str = "pro";
|
||||
pub const PROFILE_MEMBERSHIP_TIER_ULTIMATE: &str = "ultimate";
|
||||
pub const PROFILE_FEEDBACK_STATUS_OPEN: &str = "open";
|
||||
pub const TRACKING_SCOPE_KIND_SITE: &str = "site";
|
||||
pub const TRACKING_SCOPE_KIND_WORK: &str = "work";
|
||||
@@ -210,13 +202,15 @@ pub struct ProfileDailyFreePointsResponse {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileMudPointBalanceResponse {
|
||||
pub total_points: u64,
|
||||
pub permanent_points: u64,
|
||||
pub limited_points: u64,
|
||||
pub limited_expires_at: Option<String>,
|
||||
/// 每日免费泥点:北京业务日有效,跨日清零并重置。
|
||||
pub daily_free_points: u64,
|
||||
pub daily_free_reset_points: u64,
|
||||
pub daily_free_resets_at: String,
|
||||
/// 月度泥点:会员每期额度,到刷新时刻清零再发新额度。
|
||||
pub monthly_points: u64,
|
||||
pub monthly_resets_at: Option<String>,
|
||||
/// 永久泥点:不过期,创作照常扣点。
|
||||
pub permanent_points: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -249,32 +243,32 @@ pub struct ProfileRechargeProductResponse {
|
||||
pub duration_days: u32,
|
||||
pub badge_label: String,
|
||||
pub description: String,
|
||||
pub tier: String,
|
||||
pub membership_period_points: u64,
|
||||
pub membership_period_days: u32,
|
||||
pub membership_queue_limit: u32,
|
||||
pub membership_discount_bps: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileMembershipBenefitResponse {
|
||||
pub benefit_name: String,
|
||||
pub normal_value: String,
|
||||
pub month_value: String,
|
||||
pub season_value: String,
|
||||
pub year_value: String,
|
||||
pub starter_value: String,
|
||||
pub basic_value: String,
|
||||
pub pro_value: String,
|
||||
pub ultimate_value: String,
|
||||
pub struct ProfileMembershipPlanResponse {
|
||||
/// 档位 token:`normal` / `starter` / `plus` / `pro` / `max`。
|
||||
pub plan: String,
|
||||
pub title: String,
|
||||
pub rank: u8,
|
||||
pub month_price_cents: u64,
|
||||
pub year_price_cents: u64,
|
||||
pub period_points: u64,
|
||||
/// 模型权限 token:`basic` / `full`。
|
||||
pub model_access: String,
|
||||
/// 并发上限;`128` 表示不设上限。
|
||||
pub concurrent_job_limit: u32,
|
||||
pub sort_order: i32,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileMembershipResponse {
|
||||
pub status: String,
|
||||
pub tier: String,
|
||||
/// 当前档位 token;非会员与已到期都是 `normal`。
|
||||
pub plan: String,
|
||||
pub started_at: Option<String>,
|
||||
pub expires_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
@@ -282,7 +276,12 @@ pub struct ProfileMembershipResponse {
|
||||
pub cycle_resets_at: Option<String>,
|
||||
pub cycle_granted_points: u64,
|
||||
pub cycle_remaining_points: u64,
|
||||
pub cycle_period_days: u32,
|
||||
/// 当前期序号(1-based)。
|
||||
pub cycle_index: u32,
|
||||
/// 本次购买总期数(月付 1 / 年付 12)。
|
||||
pub cycle_count: u32,
|
||||
/// 周期 token:`monthly` / `yearly`。
|
||||
pub cycle_kind: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -350,12 +349,11 @@ pub struct WechatNativePaymentResponse {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileRechargeCenterResponse {
|
||||
pub wallet_balance: u64,
|
||||
pub mud_point_balance: ProfileMudPointBalanceResponse,
|
||||
pub membership: ProfileMembershipResponse,
|
||||
pub point_products: Vec<ProfileRechargeProductResponse>,
|
||||
pub membership_products: Vec<ProfileRechargeProductResponse>,
|
||||
pub benefits: Vec<ProfileMembershipBenefitResponse>,
|
||||
/// 会员档位目录(后台可改价),会员购买 / 升级的价格与权益都以它为准。
|
||||
pub membership_plans: Vec<ProfileMembershipPlanResponse>,
|
||||
pub latest_order: Option<ProfileRechargeOrderResponse>,
|
||||
pub has_points_recharged: bool,
|
||||
pub daily_free_points: ProfileDailyFreePointsResponse,
|
||||
@@ -577,11 +575,6 @@ pub struct ProfileRechargeProductConfigAdminResponse {
|
||||
pub duration_days: u32,
|
||||
pub badge_label: String,
|
||||
pub description: String,
|
||||
pub tier: String,
|
||||
pub membership_period_points: u64,
|
||||
pub membership_period_days: u32,
|
||||
pub membership_queue_limit: u32,
|
||||
pub membership_discount_bps: u32,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub created_by: String,
|
||||
@@ -666,15 +659,6 @@ pub struct AdminUpsertProfileRechargeProductRequest {
|
||||
pub badge_label: Option<String>,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
pub tier: String,
|
||||
#[serde(default)]
|
||||
pub membership_period_points: u64,
|
||||
#[serde(default)]
|
||||
pub membership_period_days: u32,
|
||||
#[serde(default)]
|
||||
pub membership_queue_limit: u32,
|
||||
#[serde(default)]
|
||||
pub membership_discount_bps: u32,
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
#[serde(default)]
|
||||
@@ -1325,13 +1309,12 @@ mod tests {
|
||||
|
||||
fn empty_mud_point_balance() -> ProfileMudPointBalanceResponse {
|
||||
ProfileMudPointBalanceResponse {
|
||||
total_points: 0,
|
||||
permanent_points: 0,
|
||||
limited_points: 0,
|
||||
limited_expires_at: None,
|
||||
daily_free_points: 0,
|
||||
daily_free_reset_points: 20,
|
||||
daily_free_resets_at: "2026-07-12T16:00:00Z".to_string(),
|
||||
monthly_points: 0,
|
||||
monthly_resets_at: None,
|
||||
permanent_points: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1608,27 +1591,27 @@ mod tests {
|
||||
#[test]
|
||||
fn profile_recharge_center_response_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(ProfileRechargeCenterResponse {
|
||||
wallet_balance: 29,
|
||||
mud_point_balance: ProfileMudPointBalanceResponse {
|
||||
total_points: 29,
|
||||
permanent_points: 4,
|
||||
limited_points: 5,
|
||||
limited_expires_at: Some("2026-05-25T10:00:00Z".to_string()),
|
||||
daily_free_points: 20,
|
||||
daily_free_reset_points: 20,
|
||||
daily_free_resets_at: "2026-07-12T16:00:00Z".to_string(),
|
||||
monthly_points: 5,
|
||||
monthly_resets_at: Some("2026-05-25T10:00:00Z".to_string()),
|
||||
permanent_points: 4,
|
||||
},
|
||||
membership: ProfileMembershipResponse {
|
||||
status: "active".to_string(),
|
||||
tier: "month".to_string(),
|
||||
plan: "plus".to_string(),
|
||||
started_at: Some("2026-04-25T10:00:00Z".to_string()),
|
||||
expires_at: Some("2026-05-25T10:00:00Z".to_string()),
|
||||
updated_at: Some("2026-04-25T10:00:00Z".to_string()),
|
||||
cycle_started_at: Some("2026-04-25T10:00:00Z".to_string()),
|
||||
cycle_resets_at: Some("2026-05-25T10:00:00Z".to_string()),
|
||||
cycle_granted_points: 800,
|
||||
cycle_granted_points: 1_150,
|
||||
cycle_remaining_points: 320,
|
||||
cycle_period_days: 30,
|
||||
cycle_index: 1,
|
||||
cycle_count: 1,
|
||||
cycle_kind: "monthly".to_string(),
|
||||
},
|
||||
point_products: vec![ProfileRechargeProductResponse {
|
||||
product_id: "points_60".to_string(),
|
||||
@@ -1638,16 +1621,21 @@ mod tests {
|
||||
points_amount: 60,
|
||||
bonus_points: 60,
|
||||
duration_days: 0,
|
||||
badge_label: "首充双倍".to_string(),
|
||||
description: "首充送60泥点".to_string(),
|
||||
tier: "normal".to_string(),
|
||||
membership_period_points: 0,
|
||||
membership_period_days: 0,
|
||||
membership_queue_limit: 0,
|
||||
membership_discount_bps: 0,
|
||||
badge_label: String::new(),
|
||||
description: "60泥点".to_string(),
|
||||
}],
|
||||
membership_plans: vec![ProfileMembershipPlanResponse {
|
||||
plan: "plus".to_string(),
|
||||
title: "Plus".to_string(),
|
||||
rank: 2,
|
||||
month_price_cents: 9_900,
|
||||
year_price_cents: 99_000,
|
||||
period_points: 1_150,
|
||||
model_access: "full".to_string(),
|
||||
concurrent_job_limit: 3,
|
||||
sort_order: 2,
|
||||
enabled: true,
|
||||
}],
|
||||
membership_products: vec![],
|
||||
benefits: vec![],
|
||||
latest_order: None,
|
||||
has_points_recharged: false,
|
||||
daily_free_points: ProfileDailyFreePointsResponse {
|
||||
@@ -1660,21 +1648,28 @@ mod tests {
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(payload["walletBalance"], json!(29));
|
||||
assert_eq!(payload["mudPointBalance"]["permanentPoints"], json!(4));
|
||||
assert_eq!(payload["mudPointBalance"]["limitedPoints"], json!(5));
|
||||
assert_eq!(payload["mudPointBalance"]["monthlyPoints"], json!(5));
|
||||
assert_eq!(payload["mudPointBalance"]["dailyFreePoints"], json!(20));
|
||||
assert_eq!(
|
||||
payload["mudPointBalance"]["monthlyResetsAt"],
|
||||
json!("2026-05-25T10:00:00Z")
|
||||
);
|
||||
assert_eq!(payload["membership"]["plan"], json!("plus"));
|
||||
assert_eq!(
|
||||
payload["membership"]["expiresAt"],
|
||||
json!("2026-05-25T10:00:00Z")
|
||||
);
|
||||
assert_eq!(payload["membership"]["cycleRemainingPoints"], json!(320));
|
||||
assert_eq!(payload["membership"]["cycleKind"], json!("monthly"));
|
||||
assert_eq!(payload["pointProducts"][0]["productId"], json!("points_60"));
|
||||
assert_eq!(payload["pointProducts"][0]["title"], json!("60泥点"));
|
||||
assert_eq!(payload["pointProducts"][0]["priceCents"], json!(600));
|
||||
assert_eq!(payload["pointProducts"][0]["description"], json!("60泥点"));
|
||||
assert_eq!(payload["membershipPlans"][0]["plan"], json!("plus"));
|
||||
assert_eq!(
|
||||
payload["pointProducts"][0]["description"],
|
||||
json!("首充送60泥点")
|
||||
payload["membershipPlans"][0]["concurrentJobLimit"],
|
||||
json!(3)
|
||||
);
|
||||
assert_eq!(payload["hasPointsRecharged"], json!(false));
|
||||
assert_eq!(payload["dailyFreePoints"]["grantedPoints"], json!(20));
|
||||
@@ -1712,11 +1707,10 @@ mod tests {
|
||||
expiration_last_error: None,
|
||||
};
|
||||
let center = ProfileRechargeCenterResponse {
|
||||
wallet_balance: 0,
|
||||
mud_point_balance: empty_mud_point_balance(),
|
||||
membership: ProfileMembershipResponse {
|
||||
status: "normal".to_string(),
|
||||
tier: "normal".to_string(),
|
||||
plan: "normal".to_string(),
|
||||
started_at: None,
|
||||
expires_at: None,
|
||||
updated_at: None,
|
||||
@@ -1724,11 +1718,12 @@ mod tests {
|
||||
cycle_resets_at: None,
|
||||
cycle_granted_points: 0,
|
||||
cycle_remaining_points: 0,
|
||||
cycle_period_days: 30,
|
||||
cycle_index: 1,
|
||||
cycle_count: 1,
|
||||
cycle_kind: "monthly".to_string(),
|
||||
},
|
||||
point_products: vec![],
|
||||
membership_products: vec![],
|
||||
benefits: vec![],
|
||||
membership_plans: vec![],
|
||||
latest_order: None,
|
||||
has_points_recharged: false,
|
||||
daily_free_points: ProfileDailyFreePointsResponse {
|
||||
@@ -1788,11 +1783,10 @@ mod tests {
|
||||
expiration_last_error: None,
|
||||
};
|
||||
let center = ProfileRechargeCenterResponse {
|
||||
wallet_balance: 0,
|
||||
mud_point_balance: empty_mud_point_balance(),
|
||||
membership: ProfileMembershipResponse {
|
||||
status: "normal".to_string(),
|
||||
tier: "normal".to_string(),
|
||||
plan: "normal".to_string(),
|
||||
started_at: None,
|
||||
expires_at: None,
|
||||
updated_at: None,
|
||||
@@ -1800,11 +1794,12 @@ mod tests {
|
||||
cycle_resets_at: None,
|
||||
cycle_granted_points: 0,
|
||||
cycle_remaining_points: 0,
|
||||
cycle_period_days: 30,
|
||||
cycle_index: 1,
|
||||
cycle_count: 1,
|
||||
cycle_kind: "monthly".to_string(),
|
||||
},
|
||||
point_products: vec![],
|
||||
membership_products: vec![],
|
||||
benefits: vec![],
|
||||
membership_plans: vec![],
|
||||
latest_order: None,
|
||||
has_points_recharged: false,
|
||||
daily_free_points: ProfileDailyFreePointsResponse {
|
||||
|
||||
Reference in New Issue
Block a user