fix: 防止用户误取消已付款订单-回调重新激活+2分钟内禁止取消+成功页30s超时提示
This commit is contained in:
23
src/db.rs
23
src/db.rs
@@ -643,7 +643,10 @@ pub async fn confirm_payment_order_by_orderno(
|
|||||||
None => return Err(AppError::NotFound("订单不存在".to_string())),
|
None => return Err(AppError::NotFound("订单不存在".to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
if status != "pending" {
|
// 支付宝回调已通过 RSA2 验证。如果订单被用户误取消,重新激活。
|
||||||
|
if status == "cancelled" {
|
||||||
|
tracing::warn!("订单 {} 已被取消,但支付宝确认已收款,重新激活并处理支付", order_no);
|
||||||
|
} else if status != "pending" {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,7 +665,7 @@ pub async fn confirm_payment_order_by_orderno(
|
|||||||
let result = sqlx::query(
|
let result = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
WITH updated_order AS (
|
WITH updated_order AS (
|
||||||
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 AND status = 'pending' RETURNING package_type, user_id
|
UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 AND status IN ('pending', 'cancelled') RETURNING package_type, user_id
|
||||||
)
|
)
|
||||||
UPDATE users SET
|
UPDATE users SET
|
||||||
is_member = true,
|
is_member = true,
|
||||||
@@ -1123,6 +1126,22 @@ pub async fn cancel_payment_order(
|
|||||||
return Err(AppError::BadRequest(format!("订单状态为 {},无法取消", status)));
|
return Err(AppError::BadRequest(format!("订单状态为 {},无法取消", status)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防止用户误取消:支付宝支付确认通常在 5-30 秒内到达
|
||||||
|
// 2 分钟内的订单不允许取消,避免用户付款后误触取消按钮
|
||||||
|
let order_age: f64 = sqlx::query_scalar(
|
||||||
|
r#"SELECT EXTRACT(EPOCH FROM (NOW() - created_at)) FROM payment_orders WHERE order_no = $1"#,
|
||||||
|
)
|
||||||
|
.bind(order_no)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AppError::Database(format!("查询订单创建时间失败: {}", e)))?;
|
||||||
|
|
||||||
|
if order_age < 120.0 {
|
||||||
|
return Err(AppError::BadRequest(
|
||||||
|
"订单刚刚创建,支付可能仍在处理中,请 2 分钟后再试".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
sqlx::query("UPDATE payment_orders SET status = 'cancelled' WHERE order_no = $1")
|
sqlx::query("UPDATE payment_orders SET status = 'cancelled' WHERE order_no = $1")
|
||||||
.bind(order_no)
|
.bind(order_no)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
|
|||||||
@@ -901,6 +901,11 @@ fn build_success_html(order_no: &str) -> String {
|
|||||||
<div class="tip">系统会自动处理,无需重复操作</div>
|
<div class="tip">系统会自动处理,无需重复操作</div>
|
||||||
<div class="order-no">订单号: {2}</div>
|
<div class="order-no">订单号: {2}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="timeoutView" style="display:none">
|
||||||
|
<p style="color:#666;font-size:14px">确认时间稍长,请返回小程序查看订单状态</p>
|
||||||
|
<p style="color:#999;font-size:12px">系统会在确认后自动为您开通会员,请勿重复支付</p>
|
||||||
|
<div class="order-no">订单号: {2}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
(function poll(){{
|
(function poll(){{
|
||||||
@@ -916,12 +921,22 @@ fn build_success_html(order_no: &str) -> String {
|
|||||||
if (data.success && data.data.order_status === 'paid') {{
|
if (data.success && data.data.order_status === 'paid') {{
|
||||||
document.getElementById('confirmedView').style.display = 'block';
|
document.getElementById('confirmedView').style.display = 'block';
|
||||||
document.getElementById('pendingView').style.display = 'none';
|
document.getElementById('pendingView').style.display = 'none';
|
||||||
|
document.getElementById('timeoutView').style.display = 'none';
|
||||||
|
}} else if (data.success && data.data.order_status === 'cancelled') {{
|
||||||
|
// 订单被取消(可能是误操作),继续轮询等待支付宝回调重新激活
|
||||||
|
setTimeout(poll, 2000);
|
||||||
}} else {{
|
}} else {{
|
||||||
setTimeout(poll, 2000);
|
setTimeout(poll, 2000);
|
||||||
}}
|
}}
|
||||||
}})
|
}})
|
||||||
.catch(function(){{ setTimeout(poll, 2000); }});
|
.catch(function(){{ setTimeout(poll, 2000); }});
|
||||||
}})();
|
}})();
|
||||||
|
// 30 秒后显示返回提示(不阻断轮询)
|
||||||
|
setTimeout(function(){{
|
||||||
|
if (document.getElementById('confirmedView').style.display !== 'block') {{
|
||||||
|
document.getElementById('timeoutView').style.display = 'block';
|
||||||
|
}}
|
||||||
|
}}, 30000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>"##,
|
</html>"##,
|
||||||
|
|||||||
Reference in New Issue
Block a user