fix: 支付确认TOCTOU竞态(AND status=pending)+定时器补充过期订单清理
This commit is contained in:
15
src/db.rs
15
src/db.rs
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user