diff --git a/src/db.rs b/src/db.rs index 533a6ce..ddb7ab9 100644 --- a/src/db.rs +++ b/src/db.rs @@ -615,6 +615,12 @@ pub async fn confirm_payment_order( // 发送支付成功通知 let _ = insert_payment_notification(pool, user_id, order_no).await; + // 验证会员到期时间是否正常 + if let Err(e) = verify_membership_after_payment(pool, order_no).await { + tracing::warn!("会员到期时间验证异常 (订单 {}): {}", order_no, e); + + } + Ok(new_expires) } @@ -685,6 +691,11 @@ pub async fn confirm_payment_order_by_orderno( let _ = insert_payment_audit_log(pool, order_no, uid, "paid", None, None).await; // 发送支付成功通知 let _ = insert_payment_notification(pool, uid, order_no).await; + + // 验证会员到期时间是否正常 + if let Err(e) = verify_membership_after_payment(pool, order_no).await { + tracing::warn!("会员到期时间验证异常 (订单 {}): {}", order_no, e); + } } Ok(()) @@ -1046,6 +1057,11 @@ pub async fn admin_force_confirm_order( ) .await; + // 验证会员到期时间是否正常 + if let Err(e) = verify_membership_after_payment(pool, order_no).await { + tracing::warn!("会员到期时间验证异常 (订单 {}): {}", order_no, e); + } + result } else { // 订单已处理,只写日志 @@ -1185,3 +1201,64 @@ pub async fn admin_refund_order( Ok(()) } + + +/// 支付确认后验证会员到期时间是否正常 +/// 用于捕获 ELSE INTERVAL '0 days' 的静默错误 +async fn verify_membership_after_payment( + pool: &PgPool, + order_no: &str, +) -> Result<(), AppError> { + #[derive(Debug, sqlx::FromRow)] + struct PaymentResult { + package_type: String, + paid_at: chrono::DateTime, + membership_expires_at: Option>, + } + + let row = sqlx::query_as::<_, PaymentResult>( + r#" + SELECT po.package_type, po.paid_at, u.membership_expires_at + FROM payment_orders po + JOIN users u ON u.id = po.user_id + WHERE po.order_no = $1 + "#, + ) + .bind(order_no) + .fetch_optional(pool) + .await + .map_err(|e| AppError::Database(format!("验证查询失败: {}", e)))?; + + let r = match row { + Some(r) => r, + None => return Err(AppError::NotFound("验证查询:订单不存在".to_string())), + }; + + if r.package_type == "permanent" { + return Ok(()); // 永久会员不累计 + } + + let expires = match r.membership_expires_at { + Some(e) => e, + None => { + let msg = format!("支付验证失败:{} 非永久套餐但会员到期为 NULL", r.package_type); + tracing::error!("{}", msg); + return Err(AppError::Internal(msg)); + } + }; + + let actual_days = (expires - r.paid_at).num_days(); + + // 预期至少 25 天(最短月卡 30 天,允许 5 天误差) + if actual_days < 25 { + let msg = format!( + "支付验证失败:套餐 {} 仅增加 {} 天会员时长(预期 >= 25 天),可能触发了 ELSE 分支", + r.package_type, actual_days + ); + tracing::error!("{}", msg); + return Err(AppError::Internal(msg)); + } + + tracing::info!("支付验证通过:{} 增加 {} 天", r.package_type, actual_days); + Ok(()) +}