fix: 添加用户取消订单 + 管理员手动退款端点
This commit is contained in:
114
src/db.rs
114
src/db.rs
@@ -972,3 +972,117 @@ pub async fn admin_force_confirm_order(
|
||||
|
||||
Ok(new_expires)
|
||||
}
|
||||
|
||||
/// 用户主动取消待支付订单
|
||||
pub async fn cancel_payment_order(
|
||||
pool: &PgPool,
|
||||
order_no: &str,
|
||||
user_id: i32,
|
||||
) -> Result<(), AppError> {
|
||||
// 检查订单有效且属于该用户
|
||||
let row = sqlx::query_as::<_, (i32, String)>(
|
||||
r#"SELECT user_id, status FROM payment_orders WHERE order_no = $1"#,
|
||||
)
|
||||
.bind(order_no)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("查询订单失败: {}", e)))?;
|
||||
|
||||
let (order_user_id, status) = match row {
|
||||
Some(r) => r,
|
||||
None => return Err(AppError::NotFound("订单不存在".to_string())),
|
||||
};
|
||||
|
||||
if order_user_id != user_id {
|
||||
return Err(AppError::Forbidden("无权操作此订单".to_string()));
|
||||
}
|
||||
|
||||
if status != "pending" {
|
||||
return Err(AppError::BadRequest(format!("订单状态为 {},无法取消", status)));
|
||||
}
|
||||
|
||||
sqlx::query("UPDATE payment_orders SET status = 'cancelled' WHERE order_no = $1")
|
||||
.bind(order_no)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("取消订单失败: {}", e)))?;
|
||||
|
||||
let _ = insert_payment_audit_log(pool, order_no, user_id, "cancelled", Some(user_id),
|
||||
Some("用户主动取消待支付订单")).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 管理员手动退款(标记订单为 refunded + 重新计算会员)
|
||||
pub async fn admin_refund_order(
|
||||
pool: &PgPool,
|
||||
order_no: &str,
|
||||
admin_user_id: i32,
|
||||
) -> Result<(), AppError> {
|
||||
// 查出订单信息
|
||||
let order = sqlx::query_as::<_, (i32, String)>(
|
||||
r#"SELECT user_id, status FROM payment_orders WHERE order_no = $1"#,
|
||||
)
|
||||
.bind(order_no)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("查询订单失败: {}", e)))?;
|
||||
|
||||
let (user_id, status) = match order {
|
||||
Some(o) => o,
|
||||
None => return Err(AppError::NotFound("订单不存在".to_string())),
|
||||
};
|
||||
|
||||
if status != "paid" && status != "pending" {
|
||||
return Err(AppError::BadRequest(format!("订单状态为 {},无法退款", status)));
|
||||
}
|
||||
|
||||
// 标记为 refunded
|
||||
sqlx::query("UPDATE payment_orders SET status = 'refunded' WHERE order_no = $1")
|
||||
.bind(order_no)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("退款标记失败: {}", e)))?;
|
||||
|
||||
// 如果订单是已支付状态,重新计算会员
|
||||
if status == "paid" {
|
||||
// 检查该用户是否有其他有效的已支付订单
|
||||
let other_active: (i64,) = sqlx::query_as(
|
||||
r#"
|
||||
SELECT COUNT(*) FROM payment_orders
|
||||
WHERE user_id = $1
|
||||
AND status = 'paid'
|
||||
AND order_no != $2
|
||||
AND (expires_at IS NULL OR expires_at > NOW())
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(order_no)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("查询其他有效订单失败: {}", e)))?;
|
||||
|
||||
if other_active.0 == 0 {
|
||||
sqlx::query(
|
||||
"UPDATE users SET is_paid = false, paid_expires_at = NULL WHERE id = $1",
|
||||
)
|
||||
.bind(user_id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("撤销会员失败: {}", e)))?;
|
||||
tracing::info!("管理员退款: 用户 {} 的会员因订单 {} 退款已被撤销", user_id, order_no);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"管理员退款: 用户 {} 有其他有效订单({}笔),保留会员",
|
||||
user_id,
|
||||
other_active.0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let detail = if status == "paid" { "管理员手动退款(已支付订单)" } else { "管理员取消订单(待支付订单)" };
|
||||
let _ = insert_payment_audit_log(pool, order_no, user_id, "refunded",
|
||||
Some(admin_user_id), Some(detail)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ pub async fn admin_force_confirm_order(
|
||||
let order_no = path.into_inner();
|
||||
tracing::info!("管理员强制确认订单: {}", order_no);
|
||||
|
||||
// 验证当前用户是否为管理员
|
||||
let current_user = db::get_user_by_id(pool.get_ref(), claims.user_id).await?;
|
||||
if !current_user.is_admin {
|
||||
return Err(AppError::Forbidden("无权限执行此操作".to_string()));
|
||||
@@ -97,3 +96,27 @@ pub async fn admin_force_confirm_order(
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// POST /api/admin/orders/{order_no}/refund
|
||||
/// 管理员手动退款(标记订单 + 重新计算会员)
|
||||
#[post("/api/admin/orders/{order_no}/refund")]
|
||||
pub async fn admin_refund_order(
|
||||
path: web::Path<String>,
|
||||
pool: web::Data<PgPool>,
|
||||
claims: web::ReqData<Claims>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let order_no = path.into_inner();
|
||||
tracing::info!("管理员退款订单: {}", order_no);
|
||||
|
||||
let current_user = db::get_user_by_id(pool.get_ref(), claims.user_id).await?;
|
||||
if !current_user.is_admin {
|
||||
return Err(AppError::Forbidden("无权限执行此操作".to_string()));
|
||||
}
|
||||
|
||||
db::admin_refund_order(pool.get_ref(), &order_no, claims.user_id).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": "订单已退款"
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ pub static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||||
pub use admin::admin_get_user;
|
||||
pub use admin::admin_update_user_payment;
|
||||
pub use admin::admin_force_confirm_order;
|
||||
pub use admin::admin_refund_order;
|
||||
pub use auth::login;
|
||||
pub use auth::mock_login;
|
||||
pub use auth::refresh_token;
|
||||
@@ -40,6 +41,7 @@ pub use weather::post_weather_data;
|
||||
pub use payment::alipay_notify;
|
||||
pub use payment::alipay_pay_page;
|
||||
pub use payment::alipay_refund_notify;
|
||||
pub use payment::cancel_order;
|
||||
pub use payment::create_order;
|
||||
pub use payment::generate_code;
|
||||
pub use payment::get_user_quota;
|
||||
|
||||
@@ -1003,6 +1003,25 @@ pub async fn sync_order(
|
||||
})))
|
||||
}
|
||||
|
||||
/// POST /api/payment/cancel-order — 用户主动取消待支付订单
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CancelOrderRequest {
|
||||
pub order_id: String,
|
||||
}
|
||||
|
||||
#[post("/api/payment/cancel-order")]
|
||||
pub async fn cancel_order(
|
||||
pool: web::Data<PgPool>,
|
||||
claims: web::ReqData<Claims>,
|
||||
body: web::Json<CancelOrderRequest>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
db::cancel_payment_order(pool.get_ref(), &body.order_id, claims.user_id).await?;
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": "订单已取消"
|
||||
})))
|
||||
}
|
||||
|
||||
/// GET /api/payment/orders — 获取当前用户的订单记录
|
||||
#[get("/api/payment/orders")]
|
||||
pub async fn get_user_orders(
|
||||
|
||||
@@ -18,7 +18,7 @@ use auth::jwt_middleware;
|
||||
use config::AppConfig;
|
||||
use db::create_pool;
|
||||
use handlers::{
|
||||
admin_force_confirm_order, admin_get_user, admin_update_user_payment, add_favorite, alipay_notify, alipay_pay_page, alipay_refund_notify,
|
||||
admin_force_confirm_order, admin_get_user, admin_refund_order, admin_update_user_payment, add_favorite, alipay_notify, alipay_pay_page, alipay_refund_notify, cancel_order,
|
||||
create_order, delete_weather, generate_code, generate_temp_token_handler, get_current_user_profile,
|
||||
get_favorites, get_user_quota, get_weather_brief, get_weather_details,
|
||||
get_user_orders, health_check, login, mock_login, mock_confirm, sync_order, payment_index, payment_login_status, payment_page, payment_success,
|
||||
@@ -108,9 +108,11 @@ fn create_server_config(
|
||||
.service(admin_get_user)
|
||||
.service(admin_update_user_payment)
|
||||
.service(admin_force_confirm_order)
|
||||
.service(admin_refund_order)
|
||||
.service(create_order)
|
||||
.service(mock_confirm)
|
||||
.service(sync_order)
|
||||
.service(cancel_order)
|
||||
.service(get_user_orders)
|
||||
.service(get_user_quota)
|
||||
.service(get_favorites)
|
||||
|
||||
Reference in New Issue
Block a user