1
This commit is contained in:
@@ -15,8 +15,11 @@ pub const DEFAULT_PLATFORM_THEME: RuntimePlatformTheme = RuntimePlatformTheme::L
|
||||
pub const DEFAULT_BROWSE_HISTORY_AUTHOR_DISPLAY_NAME: &str = "玩家";
|
||||
pub const MAX_BROWSE_HISTORY_BATCH_SIZE: usize = 100;
|
||||
pub const PROFILE_WALLET_LEDGER_LIST_LIMIT: usize = 50;
|
||||
pub const PROFILE_REFERRAL_REWARD_POINTS: u64 = 30;
|
||||
pub const PROFILE_REFERRAL_DAILY_INVITER_REWARD_LIMIT: u32 = 10;
|
||||
pub const SAVE_SNAPSHOT_VERSION: u32 = 2;
|
||||
pub const DEFAULT_SAVE_ARCHIVE_SUMMARY_TEXT: &str = "继续推进上一次保存的故事。";
|
||||
pub const PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK: &str = "mock";
|
||||
|
||||
// 运行时设置目前只冻结 light/dark 两种主题,避免各层散落字符串字面量。
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
@@ -253,6 +256,128 @@ pub struct RuntimeProfileDashboardGetInput {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileWalletLedgerSourceType {
|
||||
SnapshotSync,
|
||||
InviteInviterReward,
|
||||
InviteInviteeReward,
|
||||
PointsRecharge,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileRechargeProductKind {
|
||||
Points,
|
||||
Membership,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileMembershipStatus {
|
||||
Normal,
|
||||
Active,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileMembershipTier {
|
||||
Normal,
|
||||
Month,
|
||||
Season,
|
||||
Year,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RuntimeProfileRechargeOrderStatus {
|
||||
Paid,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeProductSnapshot {
|
||||
pub product_id: String,
|
||||
pub title: String,
|
||||
pub price_cents: u64,
|
||||
pub kind: RuntimeProfileRechargeProductKind,
|
||||
pub points_amount: u64,
|
||||
pub bonus_points: u64,
|
||||
pub duration_days: u32,
|
||||
pub badge_label: String,
|
||||
pub description: String,
|
||||
pub tier: RuntimeProfileMembershipTier,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileMembershipBenefitSnapshot {
|
||||
pub benefit_name: String,
|
||||
pub normal_value: String,
|
||||
pub month_value: String,
|
||||
pub season_value: String,
|
||||
pub year_value: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileMembershipSnapshot {
|
||||
pub user_id: String,
|
||||
pub status: RuntimeProfileMembershipStatus,
|
||||
pub tier: RuntimeProfileMembershipTier,
|
||||
pub started_at_micros: Option<i64>,
|
||||
pub expires_at_micros: Option<i64>,
|
||||
pub updated_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderSnapshot {
|
||||
pub order_id: String,
|
||||
pub user_id: String,
|
||||
pub product_id: String,
|
||||
pub product_title: String,
|
||||
pub kind: RuntimeProfileRechargeProductKind,
|
||||
pub amount_cents: u64,
|
||||
pub status: RuntimeProfileRechargeOrderStatus,
|
||||
pub payment_channel: String,
|
||||
pub paid_at_micros: i64,
|
||||
pub created_at_micros: i64,
|
||||
pub points_delta: i64,
|
||||
pub membership_expires_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeCenterSnapshot {
|
||||
pub user_id: String,
|
||||
pub wallet_balance: u64,
|
||||
pub membership: RuntimeProfileMembershipSnapshot,
|
||||
pub point_products: Vec<RuntimeProfileRechargeProductSnapshot>,
|
||||
pub membership_products: Vec<RuntimeProfileRechargeProductSnapshot>,
|
||||
pub benefits: Vec<RuntimeProfileMembershipBenefitSnapshot>,
|
||||
pub latest_order: Option<RuntimeProfileRechargeOrderSnapshot>,
|
||||
pub has_points_recharged: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeCenterProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileRechargeCenterSnapshot>,
|
||||
pub order: Option<RuntimeProfileRechargeOrderSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeCenterGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderCreateInput {
|
||||
pub user_id: String,
|
||||
pub product_id: String,
|
||||
pub payment_channel: String,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
@@ -280,6 +405,63 @@ pub struct RuntimeProfileWalletLedgerListInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralInviteCenterSnapshot {
|
||||
pub user_id: String,
|
||||
pub invite_code: String,
|
||||
pub invite_link_path: String,
|
||||
pub invited_count: u32,
|
||||
pub rewarded_invite_count: u32,
|
||||
pub today_inviter_reward_count: u32,
|
||||
pub today_inviter_reward_remaining: u32,
|
||||
pub reward_points: u64,
|
||||
pub has_redeemed_code: bool,
|
||||
pub bound_inviter_user_id: Option<String>,
|
||||
pub bound_at_micros: Option<i64>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralInviteCenterProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeReferralInviteCenterSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralInviteCenterGetInput {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralRedeemInput {
|
||||
pub user_id: String,
|
||||
pub invite_code: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralRedeemSnapshot {
|
||||
pub center: RuntimeReferralInviteCenterSnapshot,
|
||||
pub invitee_reward_granted: bool,
|
||||
pub inviter_reward_granted: bool,
|
||||
pub invitee_balance_after: u64,
|
||||
pub inviter_balance_after: u64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeReferralRedeemProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeReferralRedeemSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfilePlayedWorldSnapshot {
|
||||
@@ -333,8 +515,11 @@ pub enum RuntimeBrowseHistoryFieldError {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RuntimeProfileFieldError {
|
||||
MissingUserId,
|
||||
MissingInviteCode,
|
||||
MissingProductId,
|
||||
MissingWorldKey,
|
||||
MissingBottomTab,
|
||||
UnknownRechargeProduct,
|
||||
InvalidGameStateJson,
|
||||
InvalidCurrentStoryJson,
|
||||
}
|
||||
@@ -539,6 +724,100 @@ pub struct RuntimeProfilePlayStatsRecord {
|
||||
pub updated_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileRechargeProductRecord {
|
||||
pub product_id: String,
|
||||
pub title: String,
|
||||
pub price_cents: u64,
|
||||
pub kind: RuntimeProfileRechargeProductKind,
|
||||
pub points_amount: u64,
|
||||
pub bonus_points: u64,
|
||||
pub duration_days: u32,
|
||||
pub badge_label: String,
|
||||
pub description: String,
|
||||
pub tier: RuntimeProfileMembershipTier,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileMembershipBenefitRecord {
|
||||
pub benefit_name: String,
|
||||
pub normal_value: String,
|
||||
pub month_value: String,
|
||||
pub season_value: String,
|
||||
pub year_value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileMembershipRecord {
|
||||
pub user_id: String,
|
||||
pub status: RuntimeProfileMembershipStatus,
|
||||
pub tier: RuntimeProfileMembershipTier,
|
||||
pub started_at: Option<String>,
|
||||
pub started_at_micros: Option<i64>,
|
||||
pub expires_at: Option<String>,
|
||||
pub expires_at_micros: Option<i64>,
|
||||
pub updated_at: Option<String>,
|
||||
pub updated_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileRechargeOrderRecord {
|
||||
pub order_id: String,
|
||||
pub user_id: String,
|
||||
pub product_id: String,
|
||||
pub product_title: String,
|
||||
pub kind: RuntimeProfileRechargeProductKind,
|
||||
pub amount_cents: u64,
|
||||
pub status: RuntimeProfileRechargeOrderStatus,
|
||||
pub payment_channel: String,
|
||||
pub paid_at: String,
|
||||
pub paid_at_micros: i64,
|
||||
pub created_at: String,
|
||||
pub created_at_micros: i64,
|
||||
pub points_delta: i64,
|
||||
pub membership_expires_at: Option<String>,
|
||||
pub membership_expires_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileRechargeCenterRecord {
|
||||
pub user_id: String,
|
||||
pub wallet_balance: u64,
|
||||
pub membership: RuntimeProfileMembershipRecord,
|
||||
pub point_products: Vec<RuntimeProfileRechargeProductRecord>,
|
||||
pub membership_products: Vec<RuntimeProfileRechargeProductRecord>,
|
||||
pub benefits: Vec<RuntimeProfileMembershipBenefitRecord>,
|
||||
pub latest_order: Option<RuntimeProfileRechargeOrderRecord>,
|
||||
pub has_points_recharged: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeReferralInviteCenterRecord {
|
||||
pub user_id: String,
|
||||
pub invite_code: String,
|
||||
pub invite_link_path: String,
|
||||
pub invited_count: u32,
|
||||
pub rewarded_invite_count: u32,
|
||||
pub today_inviter_reward_count: u32,
|
||||
pub today_inviter_reward_remaining: u32,
|
||||
pub reward_points: u64,
|
||||
pub has_redeemed_code: bool,
|
||||
pub bound_inviter_user_id: Option<String>,
|
||||
pub bound_at: Option<String>,
|
||||
pub bound_at_micros: Option<i64>,
|
||||
pub updated_at: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeReferralRedeemRecord {
|
||||
pub center: RuntimeReferralInviteCenterRecord,
|
||||
pub invitee_reward_granted: bool,
|
||||
pub inviter_reward_granted: bool,
|
||||
pub invitee_balance_after: u64,
|
||||
pub inviter_balance_after: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeSnapshotRecord {
|
||||
pub user_id: String,
|
||||
@@ -598,6 +877,58 @@ pub fn build_runtime_profile_wallet_ledger_list_input(
|
||||
Ok(RuntimeProfileWalletLedgerListInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_center_get_input(
|
||||
user_id: String,
|
||||
) -> Result<RuntimeProfileRechargeCenterGetInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
Ok(RuntimeProfileRechargeCenterGetInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_order_create_input(
|
||||
user_id: String,
|
||||
product_id: String,
|
||||
payment_channel: String,
|
||||
created_at_micros: i64,
|
||||
) -> Result<RuntimeProfileRechargeOrderCreateInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
let product_id =
|
||||
normalize_required_string(product_id).ok_or(RuntimeProfileFieldError::MissingProductId)?;
|
||||
if runtime_profile_recharge_product_by_id(&product_id).is_none() {
|
||||
return Err(RuntimeProfileFieldError::UnknownRechargeProduct);
|
||||
}
|
||||
let payment_channel = normalize_required_string(payment_channel)
|
||||
.unwrap_or_else(|| PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK.to_string());
|
||||
|
||||
Ok(RuntimeProfileRechargeOrderCreateInput {
|
||||
user_id,
|
||||
product_id,
|
||||
payment_channel,
|
||||
created_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_referral_invite_center_get_input(
|
||||
user_id: String,
|
||||
) -> Result<RuntimeReferralInviteCenterGetInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
Ok(RuntimeReferralInviteCenterGetInput { user_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_referral_redeem_input(
|
||||
user_id: String,
|
||||
invite_code: String,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeReferralRedeemInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
let invite_code =
|
||||
normalize_invite_code(invite_code).ok_or(RuntimeProfileFieldError::MissingInviteCode)?;
|
||||
Ok(RuntimeReferralRedeemInput {
|
||||
user_id,
|
||||
invite_code,
|
||||
updated_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_play_stats_get_input(
|
||||
user_id: String,
|
||||
) -> Result<RuntimeProfilePlayStatsGetInput, RuntimeProfileFieldError> {
|
||||
@@ -822,6 +1153,135 @@ pub fn build_runtime_profile_wallet_ledger_entry_record(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_center_record(
|
||||
snapshot: RuntimeProfileRechargeCenterSnapshot,
|
||||
) -> RuntimeProfileRechargeCenterRecord {
|
||||
RuntimeProfileRechargeCenterRecord {
|
||||
user_id: snapshot.user_id,
|
||||
wallet_balance: snapshot.wallet_balance,
|
||||
membership: build_runtime_profile_membership_record(snapshot.membership),
|
||||
point_products: snapshot
|
||||
.point_products
|
||||
.into_iter()
|
||||
.map(build_runtime_profile_recharge_product_record)
|
||||
.collect(),
|
||||
membership_products: snapshot
|
||||
.membership_products
|
||||
.into_iter()
|
||||
.map(build_runtime_profile_recharge_product_record)
|
||||
.collect(),
|
||||
benefits: snapshot
|
||||
.benefits
|
||||
.into_iter()
|
||||
.map(build_runtime_profile_membership_benefit_record)
|
||||
.collect(),
|
||||
latest_order: snapshot
|
||||
.latest_order
|
||||
.map(build_runtime_profile_recharge_order_record),
|
||||
has_points_recharged: snapshot.has_points_recharged,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_product_record(
|
||||
snapshot: RuntimeProfileRechargeProductSnapshot,
|
||||
) -> RuntimeProfileRechargeProductRecord {
|
||||
RuntimeProfileRechargeProductRecord {
|
||||
product_id: snapshot.product_id,
|
||||
title: snapshot.title,
|
||||
price_cents: snapshot.price_cents,
|
||||
kind: snapshot.kind,
|
||||
points_amount: snapshot.points_amount,
|
||||
bonus_points: snapshot.bonus_points,
|
||||
duration_days: snapshot.duration_days,
|
||||
badge_label: snapshot.badge_label,
|
||||
description: snapshot.description,
|
||||
tier: snapshot.tier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_membership_benefit_record(
|
||||
snapshot: RuntimeProfileMembershipBenefitSnapshot,
|
||||
) -> RuntimeProfileMembershipBenefitRecord {
|
||||
RuntimeProfileMembershipBenefitRecord {
|
||||
benefit_name: snapshot.benefit_name,
|
||||
normal_value: snapshot.normal_value,
|
||||
month_value: snapshot.month_value,
|
||||
season_value: snapshot.season_value,
|
||||
year_value: snapshot.year_value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_membership_record(
|
||||
snapshot: RuntimeProfileMembershipSnapshot,
|
||||
) -> RuntimeProfileMembershipRecord {
|
||||
RuntimeProfileMembershipRecord {
|
||||
user_id: snapshot.user_id,
|
||||
status: snapshot.status,
|
||||
tier: snapshot.tier,
|
||||
started_at: snapshot.started_at_micros.map(format_utc_micros),
|
||||
started_at_micros: snapshot.started_at_micros,
|
||||
expires_at: snapshot.expires_at_micros.map(format_utc_micros),
|
||||
expires_at_micros: snapshot.expires_at_micros,
|
||||
updated_at: snapshot.updated_at_micros.map(format_utc_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_order_record(
|
||||
snapshot: RuntimeProfileRechargeOrderSnapshot,
|
||||
) -> RuntimeProfileRechargeOrderRecord {
|
||||
RuntimeProfileRechargeOrderRecord {
|
||||
order_id: snapshot.order_id,
|
||||
user_id: snapshot.user_id,
|
||||
product_id: snapshot.product_id,
|
||||
product_title: snapshot.product_title,
|
||||
kind: snapshot.kind,
|
||||
amount_cents: snapshot.amount_cents,
|
||||
status: snapshot.status,
|
||||
payment_channel: snapshot.payment_channel,
|
||||
paid_at: format_utc_micros(snapshot.paid_at_micros),
|
||||
paid_at_micros: snapshot.paid_at_micros,
|
||||
created_at: format_utc_micros(snapshot.created_at_micros),
|
||||
created_at_micros: snapshot.created_at_micros,
|
||||
points_delta: snapshot.points_delta,
|
||||
membership_expires_at: snapshot.membership_expires_at_micros.map(format_utc_micros),
|
||||
membership_expires_at_micros: snapshot.membership_expires_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_referral_invite_center_record(
|
||||
snapshot: RuntimeReferralInviteCenterSnapshot,
|
||||
) -> RuntimeReferralInviteCenterRecord {
|
||||
RuntimeReferralInviteCenterRecord {
|
||||
user_id: snapshot.user_id,
|
||||
invite_code: snapshot.invite_code,
|
||||
invite_link_path: snapshot.invite_link_path,
|
||||
invited_count: snapshot.invited_count,
|
||||
rewarded_invite_count: snapshot.rewarded_invite_count,
|
||||
today_inviter_reward_count: snapshot.today_inviter_reward_count,
|
||||
today_inviter_reward_remaining: snapshot.today_inviter_reward_remaining,
|
||||
reward_points: snapshot.reward_points,
|
||||
has_redeemed_code: snapshot.has_redeemed_code,
|
||||
bound_inviter_user_id: snapshot.bound_inviter_user_id,
|
||||
bound_at: snapshot.bound_at_micros.map(format_utc_micros),
|
||||
bound_at_micros: snapshot.bound_at_micros,
|
||||
updated_at: format_utc_micros(snapshot.updated_at_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_referral_redeem_record(
|
||||
snapshot: RuntimeReferralRedeemSnapshot,
|
||||
) -> RuntimeReferralRedeemRecord {
|
||||
RuntimeReferralRedeemRecord {
|
||||
center: build_runtime_referral_invite_center_record(snapshot.center),
|
||||
invitee_reward_granted: snapshot.invitee_reward_granted,
|
||||
inviter_reward_granted: snapshot.inviter_reward_granted,
|
||||
invitee_balance_after: snapshot.invitee_balance_after,
|
||||
inviter_balance_after: snapshot.inviter_balance_after,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_played_world_record(
|
||||
snapshot: RuntimeProfilePlayedWorldSnapshot,
|
||||
) -> RuntimeProfilePlayedWorldRecord {
|
||||
@@ -1002,16 +1462,246 @@ impl RuntimeProfileWalletLedgerSourceType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::SnapshotSync => "snapshot_sync",
|
||||
Self::InviteInviterReward => "invite_inviter_reward",
|
||||
Self::InviteInviteeReward => "invite_invitee_reward",
|
||||
Self::PointsRecharge => "points_recharge",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeProfileRechargeProductKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Points => "points",
|
||||
Self::Membership => "membership",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeProfileMembershipStatus {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "normal",
|
||||
Self::Active => "active",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeProfileMembershipTier {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "normal",
|
||||
Self::Month => "month",
|
||||
Self::Season => "season",
|
||||
Self::Year => "year",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeProfileRechargeOrderStatus {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Paid => "paid",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runtime_profile_recharge_point_products() -> Vec<RuntimeProfileRechargeProductSnapshot> {
|
||||
vec![
|
||||
build_points_recharge_product(
|
||||
"points_10",
|
||||
"10积分",
|
||||
100,
|
||||
10,
|
||||
19,
|
||||
"首充送积分",
|
||||
"首充送19积分",
|
||||
),
|
||||
build_points_recharge_product(
|
||||
"points_60",
|
||||
"60积分",
|
||||
600,
|
||||
60,
|
||||
0,
|
||||
"无首充赠礼",
|
||||
"无首充赠送",
|
||||
),
|
||||
build_points_recharge_product(
|
||||
"points_240",
|
||||
"240积分",
|
||||
2400,
|
||||
240,
|
||||
240,
|
||||
"首充双倍",
|
||||
"首充送240积分",
|
||||
),
|
||||
build_points_recharge_product(
|
||||
"points_450",
|
||||
"450积分",
|
||||
4500,
|
||||
450,
|
||||
450,
|
||||
"首充双倍",
|
||||
"首充送450积分",
|
||||
),
|
||||
build_points_recharge_product(
|
||||
"points_950",
|
||||
"950积分",
|
||||
9500,
|
||||
950,
|
||||
950,
|
||||
"首充双倍",
|
||||
"首充送950积分",
|
||||
),
|
||||
build_points_recharge_product(
|
||||
"points_1980",
|
||||
"1980积分",
|
||||
19800,
|
||||
1980,
|
||||
1980,
|
||||
"首充双倍",
|
||||
"首充送1980积分",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn runtime_profile_recharge_membership_products() -> Vec<RuntimeProfileRechargeProductSnapshot>
|
||||
{
|
||||
vec![
|
||||
build_membership_recharge_product(
|
||||
"member_month",
|
||||
"月卡",
|
||||
2800,
|
||||
30,
|
||||
RuntimeProfileMembershipTier::Month,
|
||||
),
|
||||
build_membership_recharge_product(
|
||||
"member_season",
|
||||
"季卡",
|
||||
7800,
|
||||
90,
|
||||
RuntimeProfileMembershipTier::Season,
|
||||
),
|
||||
build_membership_recharge_product(
|
||||
"member_year",
|
||||
"年卡",
|
||||
24800,
|
||||
365,
|
||||
RuntimeProfileMembershipTier::Year,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn runtime_profile_membership_benefits() -> Vec<RuntimeProfileMembershipBenefitSnapshot> {
|
||||
vec![
|
||||
RuntimeProfileMembershipBenefitSnapshot {
|
||||
benefit_name: "特权名称".to_string(),
|
||||
normal_value: "普通".to_string(),
|
||||
month_value: "月卡".to_string(),
|
||||
season_value: "季卡".to_string(),
|
||||
year_value: "年卡".to_string(),
|
||||
},
|
||||
RuntimeProfileMembershipBenefitSnapshot {
|
||||
benefit_name: "免费".to_string(),
|
||||
normal_value: "免费".to_string(),
|
||||
month_value: "¥28".to_string(),
|
||||
season_value: "¥78".to_string(),
|
||||
year_value: "¥248".to_string(),
|
||||
},
|
||||
RuntimeProfileMembershipBenefitSnapshot {
|
||||
benefit_name: "免积分回合数".to_string(),
|
||||
normal_value: "30".to_string(),
|
||||
month_value: "100".to_string(),
|
||||
season_value: "100".to_string(),
|
||||
year_value: "100".to_string(),
|
||||
},
|
||||
RuntimeProfileMembershipBenefitSnapshot {
|
||||
benefit_name: "每日签到加成".to_string(),
|
||||
normal_value: "0%".to_string(),
|
||||
month_value: "0%".to_string(),
|
||||
season_value: "+100%".to_string(),
|
||||
year_value: "+210%".to_string(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn runtime_profile_recharge_product_by_id(
|
||||
product_id: &str,
|
||||
) -> Option<RuntimeProfileRechargeProductSnapshot> {
|
||||
runtime_profile_recharge_point_products()
|
||||
.into_iter()
|
||||
.chain(runtime_profile_recharge_membership_products())
|
||||
.find(|product| product.product_id == product_id)
|
||||
}
|
||||
|
||||
fn build_points_recharge_product(
|
||||
product_id: &str,
|
||||
title: &str,
|
||||
price_cents: u64,
|
||||
points_amount: u64,
|
||||
bonus_points: u64,
|
||||
badge_label: &str,
|
||||
description: &str,
|
||||
) -> RuntimeProfileRechargeProductSnapshot {
|
||||
RuntimeProfileRechargeProductSnapshot {
|
||||
product_id: product_id.to_string(),
|
||||
title: title.to_string(),
|
||||
price_cents,
|
||||
kind: RuntimeProfileRechargeProductKind::Points,
|
||||
points_amount,
|
||||
bonus_points,
|
||||
duration_days: 0,
|
||||
badge_label: badge_label.to_string(),
|
||||
description: description.to_string(),
|
||||
tier: RuntimeProfileMembershipTier::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_membership_recharge_product(
|
||||
product_id: &str,
|
||||
title: &str,
|
||||
price_cents: u64,
|
||||
duration_days: u32,
|
||||
tier: RuntimeProfileMembershipTier,
|
||||
) -> RuntimeProfileRechargeProductSnapshot {
|
||||
RuntimeProfileRechargeProductSnapshot {
|
||||
product_id: product_id.to_string(),
|
||||
title: title.to_string(),
|
||||
price_cents,
|
||||
kind: RuntimeProfileRechargeProductKind::Membership,
|
||||
points_amount: 0,
|
||||
bonus_points: 0,
|
||||
duration_days,
|
||||
badge_label: String::new(),
|
||||
description: format!("{}天会员", duration_days),
|
||||
tier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_invite_code(value: String) -> Option<String> {
|
||||
let normalized = value
|
||||
.trim()
|
||||
.chars()
|
||||
.filter(|character| character.is_ascii_alphanumeric())
|
||||
.map(|character| character.to_ascii_uppercase())
|
||||
.collect::<String>();
|
||||
|
||||
if normalized.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RuntimeProfileFieldError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingUserId => f.write_str("profile.user_id 不能为空"),
|
||||
Self::MissingInviteCode => f.write_str("referral.invite_code 不能为空"),
|
||||
Self::MissingProductId => f.write_str("recharge.product_id 不能为空"),
|
||||
Self::MissingWorldKey => f.write_str("profile.world_key 不能为空"),
|
||||
Self::MissingBottomTab => f.write_str("runtime_snapshot.bottom_tab 不能为空"),
|
||||
Self::UnknownRechargeProduct => f.write_str("recharge.product_id 不存在"),
|
||||
Self::InvalidGameStateJson => {
|
||||
f.write_str("runtime_snapshot.game_state 必须是合法 JSON")
|
||||
}
|
||||
@@ -1268,5 +1958,38 @@ mod tests {
|
||||
RuntimeProfileWalletLedgerSourceType::SnapshotSync.as_str(),
|
||||
"snapshot_sync"
|
||||
);
|
||||
assert_eq!(
|
||||
RuntimeProfileWalletLedgerSourceType::PointsRecharge.as_str(),
|
||||
"points_recharge"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recharge_product_catalog_matches_reference_prices() {
|
||||
let point_products = runtime_profile_recharge_point_products();
|
||||
let membership_products = runtime_profile_recharge_membership_products();
|
||||
|
||||
assert_eq!(point_products.len(), 6);
|
||||
assert_eq!(point_products[0].product_id, "points_10");
|
||||
assert_eq!(point_products[0].price_cents, 100);
|
||||
assert_eq!(point_products[0].bonus_points, 19);
|
||||
assert_eq!(point_products[5].points_amount, 1980);
|
||||
assert_eq!(membership_products.len(), 3);
|
||||
assert_eq!(membership_products[0].title, "月卡");
|
||||
assert_eq!(membership_products[0].price_cents, 2800);
|
||||
assert_eq!(membership_products[2].duration_days, 365);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_recharge_order_input_rejects_unknown_product() {
|
||||
let error = build_runtime_profile_recharge_order_create_input(
|
||||
"user-1".to_string(),
|
||||
"bad-product".to_string(),
|
||||
"mock".to_string(),
|
||||
1,
|
||||
)
|
||||
.expect_err("unknown product should fail");
|
||||
|
||||
assert_eq!(error, RuntimeProfileFieldError::UnknownRechargeProduct);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user