修改前台动态获取支付是否开启
This commit is contained in:
@@ -21,6 +21,17 @@ from app.services.payment import (
|
||||
router = APIRouter(prefix="/payments", tags=["payments"])
|
||||
|
||||
|
||||
@router.get("/methods")
|
||||
async def get_payment_methods(db: AsyncSession = Depends(get_db)):
|
||||
"""Return which payment methods are enabled (from admin config)."""
|
||||
from app.services.payment import _get_payment_configs
|
||||
configs = await _get_payment_configs(db)
|
||||
return {
|
||||
"alipay": configs.get("payment_alipay_enabled", "").lower() == "true",
|
||||
"wechat": configs.get("payment_wechat_enabled", "").lower() == "true",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/recharge", response_model=PaymentOrderOut)
|
||||
async def recharge(
|
||||
req: RechargeRequest,
|
||||
@@ -30,6 +41,14 @@ async def recharge(
|
||||
if req.method not in ("wechat", "alipay"):
|
||||
raise HTTPException(status_code=400, detail="不支持的支付方式")
|
||||
|
||||
# Check if the selected payment method is enabled in admin config
|
||||
from app.services.payment import _get_payment_configs, _is_mock_mode
|
||||
configs = await _get_payment_configs(db)
|
||||
if not _is_mock_mode(configs):
|
||||
enabled_key = f"payment_{req.method}_enabled"
|
||||
if configs.get(enabled_key, "").lower() != "true":
|
||||
raise HTTPException(status_code=400, detail="该支付方式未启用")
|
||||
|
||||
result = await db.execute(
|
||||
select(RechargePackage).where(
|
||||
RechargePackage.id == req.plan,
|
||||
@@ -40,15 +59,18 @@ async def recharge(
|
||||
pkg = result.scalar_one_or_none()
|
||||
if not pkg:
|
||||
raise HTTPException(status_code=400, detail="无效的套餐")
|
||||
order = await create_recharge_order(
|
||||
db,
|
||||
current_user.id,
|
||||
credits=pkg.credits,
|
||||
price=pkg.price,
|
||||
label=pkg.name,
|
||||
bonus_credits=pkg.bonus_credits,
|
||||
method=req.method,
|
||||
)
|
||||
try:
|
||||
order = await create_recharge_order(
|
||||
db,
|
||||
current_user.id,
|
||||
credits=pkg.credits,
|
||||
price=pkg.price,
|
||||
label=pkg.name,
|
||||
bonus_credits=pkg.bonus_credits,
|
||||
method=req.method,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
return order
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,22 @@ async def create_recharge_order(
|
||||
Returns the order; for Alipay the ``qr_url`` attribute will be populated
|
||||
with the scan-to-pay URL.
|
||||
"""
|
||||
# Read config from database first
|
||||
db_configs = await _get_payment_configs(db)
|
||||
mock_mode = _is_mock_mode(db_configs)
|
||||
|
||||
# In real mode, validate that the payment method is enabled and configured
|
||||
if not mock_mode:
|
||||
enabled_key = f"payment_{method}_enabled"
|
||||
if db_configs.get(enabled_key, "").lower() != "true":
|
||||
raise ValueError("该支付方式未启用,请联系管理员")
|
||||
if method == "alipay":
|
||||
if not db_configs.get("payment_alipay_app_id") or not db_configs.get("payment_alipay_private_key"):
|
||||
raise ValueError("支付宝支付未完成配置,请联系管理员")
|
||||
elif method == "wechat":
|
||||
if not db_configs.get("payment_wechat_mch_id") or not db_configs.get("payment_wechat_api_key"):
|
||||
raise ValueError("微信支付未完成配置,请联系管理员")
|
||||
|
||||
total_credits = credits + bonus_credits
|
||||
order = PaymentOrder(
|
||||
id=generate_id(),
|
||||
@@ -110,10 +126,6 @@ async def create_recharge_order(
|
||||
db.add(order)
|
||||
await db.flush()
|
||||
|
||||
# Read config from database
|
||||
db_configs = await _get_payment_configs(db)
|
||||
mock_mode = _is_mock_mode(db_configs)
|
||||
|
||||
if mock_mode:
|
||||
# Mock: immediately complete payment
|
||||
order.status = "paid"
|
||||
|
||||
@@ -319,6 +319,10 @@ export async function getRechargePackages(): Promise<any[]> {
|
||||
return api.get('/recharge-packages');
|
||||
}
|
||||
|
||||
export async function getPaymentMethods(): Promise<{ alipay: boolean; wechat: boolean }> {
|
||||
return api.get('/payments/methods');
|
||||
}
|
||||
|
||||
export async function createRechargeOrder(planId: string, method: string = 'wechat'): Promise<any> {
|
||||
return api.post('/payments/recharge', { plan: planId, method });
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuthStore } from '../../store/useAuthStore';
|
||||
import { getMenuConfigs, getRechargePackages, createRechargeOrder, getPaymentOrders, getNotifications, markNotificationRead, getSiteInfo } from '../../api';
|
||||
import { getMenuConfigs, getRechargePackages, getPaymentMethods, createRechargeOrder, getPaymentOrders, getNotifications, markNotificationRead, getSiteInfo } from '../../api';
|
||||
import NotificationPopup from '../NotificationPopup';
|
||||
|
||||
interface MenuConfig {
|
||||
@@ -93,6 +93,7 @@ const AppLayout: React.FC = () => {
|
||||
const [paymentMethod, setPaymentMethod] = useState<string>('alipay');
|
||||
const [paying, setPaying] = useState(false);
|
||||
const pollingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const [enabledMethods, setEnabledMethods] = useState<{ alipay: boolean; wechat: boolean }>({ alipay: false, wechat: false });
|
||||
|
||||
// 监听预览弹窗状态,关闭浮动按钮
|
||||
useEffect(() => {
|
||||
@@ -140,6 +141,12 @@ const AppLayout: React.FC = () => {
|
||||
getRechargePackages().then(data => {
|
||||
setRechargeOptions(data.filter((p: any) => p.is_active !== false && p.isActive !== false));
|
||||
}).catch(() => {});
|
||||
getPaymentMethods().then(data => {
|
||||
setEnabledMethods(data);
|
||||
// Auto-select the first enabled method
|
||||
if (data.alipay) setPaymentMethod('alipay');
|
||||
else if (data.wechat) setPaymentMethod('wechat');
|
||||
}).catch(() => {});
|
||||
loadNotifications();
|
||||
}, [user]);
|
||||
|
||||
@@ -559,32 +566,44 @@ const AppLayout: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Payment method selection */}
|
||||
<div style={{ marginTop: 20, marginBottom: 8 }}>
|
||||
<Typography.Text style={{ color: '#64748b', fontSize: 13, marginBottom: 8, display: 'block' }}>选择支付方式</Typography.Text>
|
||||
<Radio.Group value={paymentMethod} onChange={(e) => setPaymentMethod(e.target.value)}
|
||||
style={{ display: 'flex', gap: 12 }}>
|
||||
<Radio.Button value="alipay" style={{
|
||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||
borderColor: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||
color: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||
}}>
|
||||
<AlipayCircleOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||
支付宝
|
||||
</Radio.Button>
|
||||
<Radio.Button value="wechat" style={{
|
||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||
borderColor: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||
color: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||
}}>
|
||||
<WechatOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||
微信支付
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
{(!enabledMethods.alipay && !enabledMethods.wechat) ? (
|
||||
<div style={{ marginTop: 20, marginBottom: 8, padding: 16, background: '#fef2f2', borderRadius: 12, border: '1px solid #fecaca' }}>
|
||||
<Typography.Text style={{ color: '#dc2626', fontSize: 13 }}>
|
||||
⚠️ 暂无可用的支付方式,请联系管理员开启支付功能
|
||||
</Typography.Text>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginTop: 20, marginBottom: 8 }}>
|
||||
<Typography.Text style={{ color: '#64748b', fontSize: 13, marginBottom: 8, display: 'block' }}>选择支付方式</Typography.Text>
|
||||
<Radio.Group value={paymentMethod} onChange={(e) => setPaymentMethod(e.target.value)}
|
||||
style={{ display: 'flex', gap: 12 }}>
|
||||
{enabledMethods.alipay && (
|
||||
<Radio.Button value="alipay" style={{
|
||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||
borderColor: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||
color: paymentMethod === 'alipay' ? '#1677ff' : undefined,
|
||||
}}>
|
||||
<AlipayCircleOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||
支付宝
|
||||
</Radio.Button>
|
||||
)}
|
||||
{enabledMethods.wechat && (
|
||||
<Radio.Button value="wechat" style={{
|
||||
flex: 1, textAlign: 'center', borderRadius: 10, height: 44, lineHeight: '42px',
|
||||
borderColor: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||
color: paymentMethod === 'wechat' ? '#07c160' : undefined,
|
||||
}}>
|
||||
<WechatOutlined style={{ fontSize: 16, marginRight: 6 }} />
|
||||
微信支付
|
||||
</Radio.Button>
|
||||
)}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button size="large" onClick={() => { setRechargeModalOpen(false); setSelectedPlan(null); }} style={{ borderRadius: 10, marginRight: 12 }}>取消</Button>
|
||||
<Button type="primary" size="large" disabled={!selectedPlan} loading={paying}
|
||||
<Button type="primary" size="large" disabled={!selectedPlan || (!enabledMethods.alipay && !enabledMethods.wechat)} loading={paying}
|
||||
onClick={async () => {
|
||||
const plan = rechargeOptions.find((opt: any) => opt.id === selectedPlan);
|
||||
if (!plan) return;
|
||||
|
||||
Reference in New Issue
Block a user