Merge branch 'master' of https://git.genarrative.world/GenarrativeAI/Genarrative
This commit is contained in:
@@ -292,6 +292,31 @@ pub fn build_runtime_profile_recharge_product_record(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_product_config_record(
|
||||
snapshot: RuntimeProfileRechargeProductConfigSnapshot,
|
||||
) -> RuntimeProfileRechargeProductConfigRecord {
|
||||
RuntimeProfileRechargeProductConfigRecord {
|
||||
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,
|
||||
enabled: snapshot.enabled,
|
||||
sort_order: snapshot.sort_order,
|
||||
created_by: snapshot.created_by,
|
||||
created_at: format_utc_micros(snapshot.created_at_micros),
|
||||
created_at_micros: snapshot.created_at_micros,
|
||||
updated_by: snapshot.updated_by,
|
||||
updated_at: format_utc_micros(snapshot.updated_at_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_membership_benefit_record(
|
||||
snapshot: RuntimeProfileMembershipBenefitSnapshot,
|
||||
) -> RuntimeProfileMembershipBenefitRecord {
|
||||
@@ -1114,9 +1139,9 @@ fn hash_runtime_profile_recharge_order_key(
|
||||
|
||||
pub fn resolve_runtime_profile_points_recharge_delta(
|
||||
product: &RuntimeProfileRechargeProductSnapshot,
|
||||
has_points_recharged: bool,
|
||||
has_product_recharged: bool,
|
||||
) -> u64 {
|
||||
let bonus_points = if has_points_recharged {
|
||||
let bonus_points = if has_product_recharged {
|
||||
0
|
||||
} else {
|
||||
product.bonus_points
|
||||
|
||||
@@ -11,7 +11,7 @@ use shared_kernel::{
|
||||
|
||||
use crate::domain::*;
|
||||
use crate::errors::*;
|
||||
use crate::{format_utc_micros, runtime_profile_recharge_product_by_id};
|
||||
use crate::format_utc_micros;
|
||||
|
||||
pub const PROFILE_USER_TAG_MAX_COUNT: usize = 8;
|
||||
pub const PROFILE_USER_TAG_MAX_CHARS: usize = 16;
|
||||
@@ -259,9 +259,6 @@ pub fn build_runtime_profile_recharge_order_create_input(
|
||||
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());
|
||||
|
||||
@@ -273,6 +270,78 @@ pub fn build_runtime_profile_recharge_order_create_input(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_product_admin_list_input(
|
||||
admin_user_id: String,
|
||||
) -> Result<RuntimeProfileRechargeProductAdminListInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
Ok(RuntimeProfileRechargeProductAdminListInput { admin_user_id })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_runtime_profile_recharge_product_admin_upsert_input(
|
||||
admin_user_id: String,
|
||||
product_id: String,
|
||||
title: String,
|
||||
price_cents: u64,
|
||||
kind: RuntimeProfileRechargeProductKind,
|
||||
points_amount: u64,
|
||||
bonus_points: u64,
|
||||
duration_days: u32,
|
||||
badge_label: String,
|
||||
description: String,
|
||||
tier: RuntimeProfileMembershipTier,
|
||||
enabled: bool,
|
||||
sort_order: i32,
|
||||
updated_at_micros: i64,
|
||||
) -> Result<RuntimeProfileRechargeProductAdminUpsertInput, RuntimeProfileFieldError> {
|
||||
let admin_user_id = normalize_runtime_profile_user_id(admin_user_id)?;
|
||||
let product_id =
|
||||
normalize_required_string(product_id).ok_or(RuntimeProfileFieldError::MissingProductId)?;
|
||||
let title =
|
||||
normalize_required_string(title).ok_or(RuntimeProfileFieldError::MissingProductTitle)?;
|
||||
if price_cents == 0 {
|
||||
return Err(RuntimeProfileFieldError::InvalidRechargeProductPrice);
|
||||
}
|
||||
match kind {
|
||||
RuntimeProfileRechargeProductKind::Points => {
|
||||
if points_amount == 0 {
|
||||
return Err(RuntimeProfileFieldError::InvalidRechargeProductPoints);
|
||||
}
|
||||
if duration_days != 0 || tier != RuntimeProfileMembershipTier::Normal {
|
||||
return Err(RuntimeProfileFieldError::InvalidRechargeProductTier);
|
||||
}
|
||||
}
|
||||
RuntimeProfileRechargeProductKind::Membership => {
|
||||
if duration_days == 0 {
|
||||
return Err(RuntimeProfileFieldError::InvalidRechargeProductDuration);
|
||||
}
|
||||
if points_amount != 0
|
||||
|| bonus_points != 0
|
||||
|| tier == RuntimeProfileMembershipTier::Normal
|
||||
{
|
||||
return Err(RuntimeProfileFieldError::InvalidRechargeProductTier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RuntimeProfileRechargeProductAdminUpsertInput {
|
||||
admin_user_id,
|
||||
product_id,
|
||||
title,
|
||||
price_cents,
|
||||
kind,
|
||||
points_amount,
|
||||
bonus_points,
|
||||
duration_days,
|
||||
badge_label: normalize_optional_string(Some(badge_label)).unwrap_or_default(),
|
||||
description: normalize_optional_string(Some(description)).unwrap_or_default(),
|
||||
tier,
|
||||
enabled,
|
||||
sort_order,
|
||||
updated_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_order_paid_input(
|
||||
order_id: String,
|
||||
paid_at_micros: i64,
|
||||
|
||||
@@ -986,6 +986,27 @@ pub struct RuntimeProfileRechargeProductSnapshot {
|
||||
pub tier: RuntimeProfileMembershipTier,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeProductConfigSnapshot {
|
||||
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,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub created_by: String,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_by: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileMembershipBenefitSnapshot {
|
||||
@@ -1054,6 +1075,47 @@ pub struct RuntimeProfileRechargeCenterProcedureResult {
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeProductAdminListInput {
|
||||
pub admin_user_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeProductAdminUpsertInput {
|
||||
pub admin_user_id: String,
|
||||
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,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeProductAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileRechargeProductConfigSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeProductAdminProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileRechargeProductConfigSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeCenterGetInput {
|
||||
@@ -1463,6 +1525,28 @@ pub struct RuntimeProfileRechargeProductRecord {
|
||||
pub tier: RuntimeProfileMembershipTier,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileRechargeProductConfigRecord {
|
||||
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,
|
||||
pub enabled: bool,
|
||||
pub sort_order: i32,
|
||||
pub created_by: String,
|
||||
pub created_at: String,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_by: String,
|
||||
pub updated_at: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileMembershipBenefitRecord {
|
||||
pub benefit_name: String,
|
||||
|
||||
@@ -74,6 +74,12 @@ pub enum RuntimeProfileFieldError {
|
||||
TaskAlreadyClaimed,
|
||||
MissingOrderId,
|
||||
MissingProductId,
|
||||
MissingProductTitle,
|
||||
InvalidRechargeProductPrice,
|
||||
InvalidRechargeProductPoints,
|
||||
InvalidRechargeProductDuration,
|
||||
InvalidRechargeProductKind,
|
||||
InvalidRechargeProductTier,
|
||||
MissingWorldKey,
|
||||
MissingBottomTab,
|
||||
MissingCheckpointSessionId,
|
||||
@@ -136,6 +142,14 @@ impl std::fmt::Display for RuntimeProfileFieldError {
|
||||
Self::TaskAlreadyClaimed => f.write_str("任务奖励已领取"),
|
||||
Self::MissingOrderId => f.write_str("recharge.order_id 不能为空"),
|
||||
Self::MissingProductId => f.write_str("recharge.product_id 不能为空"),
|
||||
Self::MissingProductTitle => f.write_str("recharge.product_title 不能为空"),
|
||||
Self::InvalidRechargeProductPrice => f.write_str("recharge.price_cents 必须大于 0"),
|
||||
Self::InvalidRechargeProductPoints => f.write_str("泥点商品 points_amount 必须大于 0"),
|
||||
Self::InvalidRechargeProductDuration => {
|
||||
f.write_str("会员商品 duration_days 必须大于 0")
|
||||
}
|
||||
Self::InvalidRechargeProductKind => f.write_str("充值商品类型无效"),
|
||||
Self::InvalidRechargeProductTier => f.write_str("会员商品 tier 无效"),
|
||||
Self::MissingWorldKey => f.write_str("profile.world_key 不能为空"),
|
||||
Self::MissingBottomTab => f.write_str("runtime_snapshot.bottom_tab 不能为空"),
|
||||
Self::MissingCheckpointSessionId => f.write_str("checkpoint.session_id 不能为空"),
|
||||
|
||||
@@ -77,6 +77,13 @@ pub fn runtime_profile_recharge_point_products() -> Vec<RuntimeProfileRechargePr
|
||||
]
|
||||
}
|
||||
|
||||
/// 中文注释:保留旧展示 helper 的兼容入口;首充资格已改为按商品档位在配置表侧计算。
|
||||
pub fn resolve_runtime_profile_recharge_point_products(
|
||||
_has_points_recharged: bool,
|
||||
) -> Vec<RuntimeProfileRechargeProductSnapshot> {
|
||||
runtime_profile_recharge_point_products()
|
||||
}
|
||||
|
||||
pub fn runtime_profile_recharge_membership_products() -> Vec<RuntimeProfileRechargeProductSnapshot>
|
||||
{
|
||||
vec![
|
||||
@@ -709,16 +716,33 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_recharge_order_input_rejects_unknown_product() {
|
||||
let error = build_runtime_profile_recharge_order_create_input(
|
||||
fn recharge_point_products_do_not_hide_all_first_bonus_by_account_flag() {
|
||||
let first_recharge_products = resolve_runtime_profile_recharge_point_products(false);
|
||||
assert_eq!(first_recharge_products[0].bonus_points, 60);
|
||||
assert_eq!(first_recharge_products[0].badge_label, "首充双倍");
|
||||
assert_eq!(first_recharge_products[0].description, "首充送60泥点");
|
||||
|
||||
let repeated_recharge_products = resolve_runtime_profile_recharge_point_products(true);
|
||||
assert_eq!(repeated_recharge_products[0].bonus_points, 60);
|
||||
assert_eq!(repeated_recharge_products[0].badge_label, "首充双倍");
|
||||
assert_eq!(repeated_recharge_products[0].description, "首充送60泥点");
|
||||
assert_eq!(repeated_recharge_products[5].bonus_points, 3280);
|
||||
assert_eq!(repeated_recharge_products[5].badge_label, "首充双倍");
|
||||
assert_eq!(repeated_recharge_products[5].description, "首充送3280泥点");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_recharge_order_input_accepts_configured_product_id_later() {
|
||||
let input = build_runtime_profile_recharge_order_create_input(
|
||||
"user-1".to_string(),
|
||||
"bad-product".to_string(),
|
||||
"custom-points-600".to_string(),
|
||||
"mock".to_string(),
|
||||
1,
|
||||
)
|
||||
.expect_err("unknown product should fail");
|
||||
.expect("product existence is validated against database config later");
|
||||
|
||||
assert_eq!(error, RuntimeProfileFieldError::UnknownRechargeProduct);
|
||||
assert_eq!(input.product_id, "custom-points-600");
|
||||
assert_eq!(input.payment_channel, "mock");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user