This commit is contained in:
@@ -238,6 +238,60 @@ pub fn list_profile_wallet_ledger(
|
||||
}
|
||||
}
|
||||
|
||||
// 资产生成由 Axum 调用外部模型,钱包扣费必须先在 SpacetimeDB 内原子落账。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn consume_profile_wallet_points_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileWalletAdjustmentInput,
|
||||
) -> RuntimeProfileWalletAdjustmentProcedureResult {
|
||||
match ctx.try_with_tx(|tx| {
|
||||
apply_profile_wallet_adjustment(
|
||||
tx,
|
||||
input.clone(),
|
||||
RuntimeProfileWalletLedgerSourceType::AssetGenerationConsume,
|
||||
true,
|
||||
)
|
||||
}) {
|
||||
Ok(record) => RuntimeProfileWalletAdjustmentProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileWalletAdjustmentProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 生成链路失败时由 Axum 调用退款,ledger_id 幂等保证重复补偿不会重复加钱。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn refund_profile_wallet_points_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: RuntimeProfileWalletAdjustmentInput,
|
||||
) -> RuntimeProfileWalletAdjustmentProcedureResult {
|
||||
match ctx.try_with_tx(|tx| {
|
||||
apply_profile_wallet_adjustment(
|
||||
tx,
|
||||
input.clone(),
|
||||
RuntimeProfileWalletLedgerSourceType::AssetGenerationRefund,
|
||||
false,
|
||||
)
|
||||
}) {
|
||||
Ok(record) => RuntimeProfileWalletAdjustmentProcedureResult {
|
||||
ok: true,
|
||||
record: Some(record),
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileWalletAdjustmentProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// play stats 与 dashboard 共用 dashboard projection 的 total_play_time / updated_at,避免 Axum 侧拼装。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_profile_play_stats(
|
||||
@@ -1370,15 +1424,91 @@ fn apply_profile_wallet_delta(
|
||||
ledger_id: &str,
|
||||
created_at: Timestamp,
|
||||
) -> Result<u64, String> {
|
||||
let amount_delta =
|
||||
i64::try_from(amount_delta).map_err(|_| "profile.wallet_amount 超出上限".to_string())?;
|
||||
apply_profile_wallet_signed_delta(
|
||||
ctx,
|
||||
user_id,
|
||||
amount_delta,
|
||||
source_type,
|
||||
ledger_id,
|
||||
created_at,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_profile_wallet_adjustment(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileWalletAdjustmentInput,
|
||||
source_type: RuntimeProfileWalletLedgerSourceType,
|
||||
consume: bool,
|
||||
) -> Result<RuntimeProfileDashboardSnapshot, String> {
|
||||
let validated_input = build_runtime_profile_wallet_adjustment_input(
|
||||
input.user_id,
|
||||
input.amount,
|
||||
input.ledger_id,
|
||||
input.created_at_micros,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let created_at = Timestamp::from_micros_since_unix_epoch(validated_input.created_at_micros);
|
||||
let amount_delta = if consume {
|
||||
-(validated_input.amount as i64)
|
||||
} else {
|
||||
validated_input.amount as i64
|
||||
};
|
||||
|
||||
apply_profile_wallet_signed_delta(
|
||||
ctx,
|
||||
&validated_input.user_id,
|
||||
amount_delta,
|
||||
source_type,
|
||||
&validated_input.ledger_id,
|
||||
created_at,
|
||||
true,
|
||||
)?;
|
||||
get_profile_dashboard_snapshot(
|
||||
ctx,
|
||||
RuntimeProfileDashboardGetInput {
|
||||
user_id: validated_input.user_id,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_profile_wallet_signed_delta(
|
||||
ctx: &ReducerContext,
|
||||
user_id: &str,
|
||||
amount_delta: i64,
|
||||
source_type: RuntimeProfileWalletLedgerSourceType,
|
||||
ledger_id: &str,
|
||||
created_at: Timestamp,
|
||||
idempotent: bool,
|
||||
) -> Result<u64, String> {
|
||||
if idempotent
|
||||
&& ctx
|
||||
.db
|
||||
.profile_wallet_ledger()
|
||||
.wallet_ledger_id()
|
||||
.find(&ledger_id.to_string())
|
||||
.is_some()
|
||||
{
|
||||
return Ok(profile_wallet_balance(ctx, user_id));
|
||||
}
|
||||
|
||||
let current = ctx
|
||||
.db
|
||||
.profile_dashboard_state()
|
||||
.user_id()
|
||||
.find(&user_id.to_string());
|
||||
let previous_balance = current.as_ref().map(|row| row.wallet_balance).unwrap_or(0);
|
||||
let next_balance = previous_balance
|
||||
.checked_add(amount_delta)
|
||||
.ok_or_else(|| "profile.wallet_balance 超出上限".to_string())?;
|
||||
let next_balance = if amount_delta >= 0 {
|
||||
previous_balance
|
||||
.checked_add(amount_delta as u64)
|
||||
.ok_or_else(|| "profile.wallet_balance 超出上限".to_string())?
|
||||
} else {
|
||||
previous_balance
|
||||
.checked_sub(amount_delta.unsigned_abs())
|
||||
.ok_or_else(|| "叙世币余额不足".to_string())?
|
||||
};
|
||||
let created_state_at = current
|
||||
.as_ref()
|
||||
.map(|row| row.created_at)
|
||||
@@ -1413,7 +1543,7 @@ fn apply_profile_wallet_delta(
|
||||
ctx.db.profile_wallet_ledger().insert(ProfileWalletLedger {
|
||||
wallet_ledger_id: ledger_id.to_string(),
|
||||
user_id: user_id.to_string(),
|
||||
amount_delta: amount_delta as i64,
|
||||
amount_delta,
|
||||
balance_after: next_balance,
|
||||
source_type,
|
||||
created_at,
|
||||
|
||||
Reference in New Issue
Block a user