This commit is contained in:
2026-05-15 02:41:43 +08:00
39 changed files with 2539 additions and 200 deletions

View File

@@ -1069,10 +1069,47 @@ pub fn build_runtime_profile_recharge_order_id(
created_at_micros: i64,
product_id: &str,
) -> String {
format!(
"recharge:{}",
build_runtime_profile_recharge_wallet_ledger_id(user_id, created_at_micros, product_id)
)
// 微信支付 v3 的 out_trade_no 只接受较短的字母、数字和部分符号。
// 订单号同时作为本地 profile_recharge_order 主键,因此统一使用可支付渠道兼容的紧凑格式。
let timestamp = encode_runtime_profile_recharge_order_base36(created_at_micros.unsigned_abs());
let hash = hash_runtime_profile_recharge_order_key(user_id, product_id, created_at_micros);
format!("rcg{timestamp}{:010x}", hash & 0x0000_0003_ffff_ffff)
}
fn encode_runtime_profile_recharge_order_base36(mut value: u64) -> String {
const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
if value == 0 {
return "0".to_string();
}
let mut buffer = Vec::new();
while value > 0 {
buffer.push(DIGITS[(value % 36) as usize] as char);
value /= 36;
}
buffer.iter().rev().collect()
}
fn hash_runtime_profile_recharge_order_key(
user_id: &str,
product_id: &str,
created_at_micros: i64,
) -> u64 {
let mut hash = 14_695_981_039_346_656_037u64;
for byte in user_id
.trim()
.as_bytes()
.iter()
.copied()
.chain([b':'])
.chain(product_id.trim().as_bytes().iter().copied())
.chain([b':'])
.chain(created_at_micros.to_le_bytes())
{
hash ^= u64::from(byte);
hash = hash.wrapping_mul(1_099_511_628_211);
}
hash
}
pub fn resolve_runtime_profile_points_recharge_delta(

View File

@@ -242,6 +242,14 @@ pub fn build_runtime_profile_recharge_center_get_input(
Ok(RuntimeProfileRechargeCenterGetInput { user_id })
}
pub fn build_runtime_profile_recharge_order_get_input(
order_id: String,
) -> Result<RuntimeProfileRechargeOrderGetInput, RuntimeProfileFieldError> {
let order_id =
normalize_required_string(order_id).ok_or(RuntimeProfileFieldError::MissingOrderId)?;
Ok(RuntimeProfileRechargeOrderGetInput { order_id })
}
pub fn build_runtime_profile_recharge_order_create_input(
user_id: String,
product_id: String,

View File

@@ -1060,6 +1060,12 @@ pub struct RuntimeProfileRechargeCenterGetInput {
pub user_id: String,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RuntimeProfileRechargeOrderGetInput {
pub order_id: String,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RuntimeProfileRechargeOrderCreateInput {

View File

@@ -733,9 +733,13 @@ mod tests {
build_runtime_profile_recharge_wallet_ledger_id("user-1", 200, "points_60"),
"user-1:200:points_60"
);
assert_eq!(
build_runtime_profile_recharge_order_id("user-1", 200, "points_60"),
"recharge:user-1:200:points_60"
let order_id = build_runtime_profile_recharge_order_id("user-1", 200, "points_60");
assert!(order_id.starts_with("rcg"));
assert!(order_id.len() <= 32);
assert!(
order_id
.chars()
.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit())
);
assert_eq!(
build_runtime_profile_redeem_code_usage_id("GIFT", "user-1", 300, 2),