feat: 实现 Mock 支付功能,修复支付激活会员问题

- 当未配置 ALIPAY_* 环境变量时,自动启用 Mock 支付模式
- Mock 支付页面点击按钮后调用 /api/payment/mock-confirm 激活会员
- 更新 AGENTS.md 文档说明 Mock 支付配置和使用方法
This commit is contained in:
2026-04-28 10:17:26 +08:00
parent ef246db31c
commit 2046638721
4 changed files with 144 additions and 6 deletions

View File

@@ -610,9 +610,55 @@ pub async fn payment_page(
let return_url = format!("{}/payment/success?order_no={}", base_url, order_no);
let Some(config) = AlipayConfig::from_env() else {
let jwt_for_mock = token.clone();
let mock_html = format!(r#"<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>模拟支付</title></head>
<body style="font-family:-apple-system;padding:40px;text-align:center">
<h2 style="color:#52c41a">模拟支付环境</h2>
<p style="color:#666">当前为沙箱模拟环境,无需真实支付</p>
<p style="color:#999;font-size:14px">订单号: {}</p>
<p style="color:#999;font-size:14px">套餐: {}</p>
<button id="confirmBtn" onclick="confirmMockPay()" style="padding:12px 32px;font-size:16px;background:#1677ff;color:#fff;border:none;border-radius:4px;cursor:pointer">确认模拟支付</button>
<p id="result"></p>
<p><a href="/payment" style="color:#1677ff">返回重试</a></p>
<script>
function confirmMockPay() {{
var btn = document.getElementById('confirmBtn');
btn.disabled = true;
btn.textContent = '处理中...';
fetch('/api/payment/mock-confirm', {{
method: 'POST',
headers: {{
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'
}},
body: JSON.stringify({{order_id: '{}'}})
}})
.then(r => r.json())
.then(data => {{
if (data.success) {{
document.getElementById('result').innerHTML = '<span style="color:#52c41a">支付成功!</span>';
setTimeout(() => window.location.href = '/payment?mock=1', 1500);
}} else {{
document.getElementById('result').innerHTML = '<span style="color:#f5222d">失败: ' + (data.error || '未知错误') + '</span>';
btn.disabled = false;
btn.textContent = '重试';
}}
}})
.catch(e => {{
document.getElementById('result').innerHTML = '<span style="color:#f5222d">网络错误</span>';
btn.disabled = false;
btn.textContent = '重试';
}});
}}
</script>
</body>
</html>
"#, order_no, pkg.display_name, jwt_for_mock, order_no);
return Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(format_error_html("支付配置不完整,请联系管理员", &order_no)));
.body(mock_html));
};
let total_amount_str = format!("{:.2}", pkg.amount as f64 / 100.0);
@@ -665,9 +711,55 @@ pub async fn alipay_pay_page(
let return_url = format!("{}/payment/success?order_no={}", base_url, query.order_no);
let Some(config) = AlipayConfig::from_env() else {
let jwt_for_mock = token.clone();
let mock_html = format!(r#"<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>模拟支付</title></head>
<body style="font-family:-apple-system;padding:40px;text-align:center">
<h2 style="color:#52c41a">模拟支付环境</h2>
<p style="color:#666">当前为沙箱模拟环境,无需真实支付</p>
<p style="color:#999;font-size:14px">订单号: {}</p>
<p style="color:#999;font-size:14px">套餐: {}</p>
<button id="confirmBtn" onclick="confirmMockPay()" style="padding:12px 32px;font-size:16px;background:#1677ff;color:#fff;border:none;border-radius:4px;cursor:pointer">确认模拟支付</button>
<p id="result"></p>
<p><a href="/payment" style="color:#1677ff">返回重试</a></p>
<script>
function confirmMockPay() {{
var btn = document.getElementById('confirmBtn');
btn.disabled = true;
btn.textContent = '处理中...';
fetch('/api/payment/mock-confirm', {{
method: 'POST',
headers: {{
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'
}},
body: JSON.stringify({{order_id: '{}'}})
}})
.then(r => r.json())
.then(data => {{
if (data.success) {{
document.getElementById('result').innerHTML = '<span style="color:#52c41a">支付成功!</span>';
setTimeout(() => window.location.href = '/payment?mock=1', 1500);
}} else {{
document.getElementById('result').innerHTML = '<span style="color:#f5222d">失败: ' + (data.error || '未知错误') + '</span>';
btn.disabled = false;
btn.textContent = '重试';
}}
}})
.catch(e => {{
document.getElementById('result').innerHTML = '<span style="color:#f5222d">网络错误</span>';
btn.disabled = false;
btn.textContent = '重试';
}});
}}
</script>
</body>
</html>
"#, query.order_no, pkg.display_name, jwt_for_mock, query.order_no);
return Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(format_error_html("支付配置不完整,请联系管理员", &query.order_no)));
.body(mock_html));
};
let total_amount_str = format!("{:.2}", pkg.amount as f64 / 100.0);