fix: 支付确认TOCTOU竞态(AND status=pending)+定时器补充过期订单清理

This commit is contained in:
2026-06-10 14:11:47 +08:00
parent c00e391c9b
commit e672f00b66
2 changed files with 14 additions and 4 deletions

View File

@@ -585,7 +585,7 @@ pub async fn confirm_payment_order(
let new_expires: Option<chrono::DateTime<chrono::Utc>> = sqlx::query_scalar(
r#"
WITH updated_order AS (
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 RETURNING package_type
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 AND status = 'pending' RETURNING package_type
)
UPDATE users SET
is_member = true,
@@ -658,10 +658,11 @@ pub async fn confirm_payment_order_by_orderno(
.flatten();
// 一次性完成:更新订单状态 + 累加计算新的到期时间
sqlx::query(
// 加上 AND status = 'pending' 防止竞态覆盖已取消/退款的订单
let result = sqlx::query(
r#"
WITH updated_order AS (
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 RETURNING package_type, user_id
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 AND status = 'pending' RETURNING package_type, user_id
)
UPDATE users SET
is_member = true,
@@ -686,6 +687,12 @@ pub async fn confirm_payment_order_by_orderno(
.await
.map_err(|e| AppError::Database(format!("支付确认失败: {}", e)))?;
// 0 行 update 说明订单已被取消/退款/已确认(竞态保护生效)
if result.rows_affected() == 0 {
tracing::warn!("支付确认: 订单 {} 状态已变更,跳过处理", order_no);
return Ok(());
}
// 审计日志
if let Some(uid) = user_id {
let _ = insert_payment_audit_log(pool, order_no, uid, "paid", None, None).await;
@@ -1019,7 +1026,7 @@ pub async fn admin_force_confirm_order(
let result = sqlx::query_scalar::<_, Option<chrono::DateTime<chrono::Utc>>>(
r#"
WITH updated_order AS (
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 RETURNING package_type
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 AND status = 'pending' RETURNING package_type
)
UPDATE users SET
is_member = true,

View File

@@ -275,6 +275,9 @@ async fn main() -> std::io::Result<()> {
// 会员到期前 7 天提醒
let _ = db::check_member_expiry_soon(&pool_clone).await;
// 清理超过 24 小时的过期待支付订单
let _ = db::cleanup_expired_pending_orders(&pool_clone, None).await;
// 清理过期的 refresh_token
let _ = db::cleanup_expired_refresh_tokens(&pool_clone).await;
}