fix: 修复付费机制失效处理的5个后端缺口
- 新增POST /payment/refund-notify支付宝退款Webhook - 新增待支付订单自动清理(超过24h→cancelled, 含启动清理) - 新增web登录码过期启动清理 - 永久会员用NULL替代硬编码2099-12-31 - 修复缺失的is_paid_active数据库函数(改用内联SQL)
This commit is contained in:
@@ -713,17 +713,22 @@ pub async fn web_login_auto_confirm(
|
||||
}
|
||||
};
|
||||
|
||||
let (is_paid_active, paid_expires_at): (bool, Option<String>) =
|
||||
match sqlx::query_as::<_, (bool, Option<chrono::DateTime<chrono::Utc>>)>(
|
||||
"SELECT is_paid_active($1)",
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_optional(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
Ok(Some((active, expires))) => (active, expires.map(|e| e.to_rfc3339())),
|
||||
_ => (false, None),
|
||||
};
|
||||
let paid_info = sqlx::query_as::<_, (bool, Option<chrono::DateTime<chrono::Utc>>)>(
|
||||
"SELECT is_paid, paid_expires_at FROM users WHERE id = $1",
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_optional(pool.get_ref())
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let (is_paid_active, paid_expires_at): (bool, Option<String>) = match paid_info {
|
||||
Some((is_paid, expires)) => {
|
||||
let active = is_paid && expires.map_or(true, |e| e > Utc::now());
|
||||
(active, expires.map(|e| e.to_rfc3339()))
|
||||
}
|
||||
None => (false, None),
|
||||
};
|
||||
|
||||
let base_url = std::env::var("APP_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://dev.xmclassmate.top".to_string());
|
||||
|
||||
@@ -38,6 +38,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::create_order;
|
||||
pub use payment::generate_code;
|
||||
pub use payment::get_user_quota;
|
||||
|
||||
@@ -844,6 +844,61 @@ pub async fn payment_success(
|
||||
.body(html)
|
||||
}
|
||||
|
||||
// ===== Handler: POST /payment/refund-notify — 支付宝退款异步回调 =====
|
||||
|
||||
#[post("/payment/refund-notify")]
|
||||
pub async fn alipay_refund_notify(
|
||||
pool: web::Data<PgPool>,
|
||||
body: web::Form<BTreeMap<String, String>>,
|
||||
) -> HttpResponse {
|
||||
let body = body.into_inner();
|
||||
|
||||
let out_trade_no = body.get("out_trade_no").cloned().unwrap_or_default();
|
||||
let refund_status = body.get("refund_status").cloned().unwrap_or_default();
|
||||
|
||||
tracing::info!(
|
||||
"收到支付宝退款回调: out_trade_no={}, refund_status={}",
|
||||
out_trade_no,
|
||||
refund_status
|
||||
);
|
||||
|
||||
// 只处理成功的退款
|
||||
if refund_status != "REFUND_SUCCESS" {
|
||||
return HttpResponse::Ok().body("success");
|
||||
}
|
||||
|
||||
// 验证 RSA2 签名
|
||||
let Some(config) = AlipayConfig::from_env() else {
|
||||
tracing::warn!("支付宝配置不存在,无法验证退款签名");
|
||||
return HttpResponse::Ok().body("fail");
|
||||
};
|
||||
|
||||
let sign = body.get("sign").cloned().unwrap_or_default();
|
||||
let sign_source: String = body
|
||||
.iter()
|
||||
.filter(|(k, _)| *k != "sign" && *k != "sign_type")
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join("&");
|
||||
|
||||
if let Err(e) = rsa2_verify(&sign_source, &sign, &config.alipay_public_key) {
|
||||
tracing::warn!("支付宝退款签名验证失败: {}", e);
|
||||
return HttpResponse::Ok().body("fail");
|
||||
}
|
||||
|
||||
// 处理退款:标记订单 + 撤销会员
|
||||
match db::refund_payment_order(pool.get_ref(), &out_trade_no).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("订单 {} 退款处理成功", out_trade_no);
|
||||
HttpResponse::Ok().body("success")
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("订单 {} 退款处理失败: {}", out_trade_no, e);
|
||||
HttpResponse::Ok().body("fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 旧的 API Handler(保持兼容)=====
|
||||
|
||||
/// POST /api/payment/create-order
|
||||
|
||||
Reference in New Issue
Block a user