282 lines
13 KiB
TypeScript
282 lines
13 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
Button, Card, Form, Input, message, Switch, Typography, InputNumber, Space,
|
||
} from 'antd';
|
||
import {
|
||
SaveOutlined, WechatOutlined, AlipayCircleOutlined, DollarOutlined, ClockCircleOutlined,
|
||
} from '@ant-design/icons';
|
||
import { getPaymentConfigs, batchUpdatePaymentConfigs } from '../api';
|
||
|
||
const AdminPaymentConfig: React.FC = () => {
|
||
const [saving, setSaving] = useState(false);
|
||
const [wechatEnabled, setWechatEnabled] = useState(false);
|
||
const [alipayEnabled, setAlipayEnabled] = useState(false);
|
||
const [mockMode, setMockMode] = useState(false);
|
||
const [orderTimeout, setOrderTimeout] = useState(180);
|
||
const [form] = Form.useForm();
|
||
|
||
const load = async () => {
|
||
try {
|
||
const data = await getPaymentConfigs();
|
||
const map: Record<string, string> = {};
|
||
data.forEach((c: any) => { map[c.key] = c.value; });
|
||
form.setFieldsValue({
|
||
wechat_appid: map['payment_wechat_appid'] || '',
|
||
wechat_mch_id: map['payment_wechat_mch_id'] || '',
|
||
wechat_private_key: map['payment_wechat_private_key'] || '',
|
||
wechat_cert_serial_no: map['payment_wechat_cert_serial_no'] || '',
|
||
wechat_api_v3_key: map['payment_wechat_api_v3_key'] || '',
|
||
wechat_notify_url: map['payment_wechat_notify_url'] || '',
|
||
wechat_public_key: map['payment_wechat_public_key'] || '',
|
||
wechat_public_key_id: map['payment_wechat_public_key_id'] || '',
|
||
alipay_app_id: map['payment_alipay_app_id'] || '',
|
||
alipay_private_key: map['payment_alipay_private_key'] || '',
|
||
alipay_public_key: map['payment_alipay_public_key'] || '',
|
||
alipay_notify_url: map['payment_alipay_notify_url'] || '',
|
||
alipay_gateway: map['payment_alipay_gateway'] || '',
|
||
order_timeout: map['payment_order_timeout'] || '180',
|
||
});
|
||
setWechatEnabled(map['payment_wechat_enabled'] === 'true');
|
||
setAlipayEnabled(map['payment_alipay_enabled'] === 'true');
|
||
setMockMode(map['payment_mock'] === 'true');
|
||
setOrderTimeout(parseInt(map['payment_order_timeout'] || '180', 10));
|
||
} catch {
|
||
message.error('加载支付配置失败');
|
||
}
|
||
};
|
||
|
||
useEffect(() => { load(); }, []);
|
||
|
||
const handleSave = async () => {
|
||
try {
|
||
const values = await form.validateFields();
|
||
setSaving(true);
|
||
await batchUpdatePaymentConfigs({
|
||
payment_mock: String(mockMode),
|
||
payment_wechat_enabled: String(wechatEnabled),
|
||
payment_wechat_appid: values.wechat_appid || '',
|
||
payment_wechat_mch_id: values.wechat_mch_id || '',
|
||
payment_wechat_private_key: values.wechat_private_key || '',
|
||
payment_wechat_cert_serial_no: values.wechat_cert_serial_no || '',
|
||
payment_wechat_api_v3_key: values.wechat_api_v3_key || '',
|
||
payment_wechat_notify_url: values.wechat_notify_url || '',
|
||
payment_wechat_public_key: values.wechat_public_key || '',
|
||
payment_wechat_public_key_id: values.wechat_public_key_id || '',
|
||
payment_alipay_enabled: String(alipayEnabled),
|
||
payment_alipay_app_id: values.alipay_app_id || '',
|
||
payment_alipay_private_key: values.alipay_private_key || '',
|
||
payment_alipay_public_key: values.alipay_public_key || '',
|
||
payment_alipay_notify_url: values.alipay_notify_url || '',
|
||
payment_alipay_gateway: values.alipay_gateway || '',
|
||
payment_order_timeout: String(values.order_timeout || 180),
|
||
});
|
||
message.success('支付配置已保存');
|
||
load();
|
||
} catch {
|
||
message.error('保存失败');
|
||
} finally {
|
||
setSaving(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div style={{ maxWidth: '100%' }}>
|
||
{/* 上边一排:通用设置 + 模拟支付模式 */}
|
||
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', marginBottom: 24 }}>
|
||
{/* 通用设置:订单超时时间 */}
|
||
<div style={{ flex: '1 1 400px', minWidth: 400 }}>
|
||
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||
<div style={{
|
||
width: 40, height: 40, borderRadius: 10,
|
||
background: 'rgba(99,102,241,0.08)',
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
fontSize: 20, color: '#6366f1',
|
||
}}><ClockCircleOutlined /></div>
|
||
<div>
|
||
<Typography.Text strong style={{ fontSize: 14 }}>订单超时时间</Typography.Text>
|
||
</div>
|
||
</div>
|
||
<Form form={form} layout="inline">
|
||
<Form.Item name="order_timeout" style={{ margin: 0 }}>
|
||
<Space.Compact style={{ width: 140 }}>
|
||
<InputNumber
|
||
min={30}
|
||
max={86400}
|
||
placeholder="180"
|
||
style={{ width: '100%' }}
|
||
size="middle"
|
||
/>
|
||
<Typography.Text>秒</Typography.Text>
|
||
</Space.Compact>
|
||
</Form.Item>
|
||
</Form>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 模拟支付模式 */}
|
||
<div style={{ flex: '1 1 400px', minWidth: 400 }}>
|
||
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5', background: mockMode ? '#fff7e6' : '#fafbff' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||
<div style={{
|
||
width: 40, height: 40, borderRadius: 10,
|
||
background: mockMode ? 'rgba(245,158,11,0.15)' : 'rgba(99,102,241,0.08)',
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
fontSize: 20, color: mockMode ? '#f59e0b' : '#6366f1',
|
||
}}><DollarOutlined /></div>
|
||
<div>
|
||
<Typography.Text strong style={{ fontSize: 14 }}>模拟支付模式</Typography.Text>
|
||
</div>
|
||
</div>
|
||
<Switch checked={mockMode} onChange={setMockMode} checkedChildren="已开启" unCheckedChildren="已关闭" />
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 下边一排:微信支付 + 支付宝 */}
|
||
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap' }}>
|
||
{/* 微信支付 */}
|
||
<div style={{ flex: '1 1 400px', minWidth: 400 }}>
|
||
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||
<div style={{
|
||
width: 44, height: 44, borderRadius: 10,
|
||
background: 'rgba(7,193,96,0.08)',
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
fontSize: 22, color: '#07c160',
|
||
}}><WechatOutlined /></div>
|
||
<div>
|
||
<Typography.Title level={5} style={{ margin: 0 }}>微信支付</Typography.Title>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>微信商户号支付配置</Typography.Text>
|
||
</div>
|
||
</div>
|
||
<Switch checked={wechatEnabled} onChange={setWechatEnabled} checkedChildren="已启用" unCheckedChildren="未启用" />
|
||
</div>
|
||
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item name="wechat_appid" label="AppID">
|
||
<Input placeholder="微信支付 AppID" size="large" disabled={!wechatEnabled} />
|
||
</Form.Item>
|
||
<Form.Item name="wechat_mch_id" label="商户号 (MchID)">
|
||
<Input placeholder="微信支付商户号" size="large" disabled={!wechatEnabled} />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="wechat_private_key"
|
||
label="商户私钥"
|
||
extra={`
|
||
⚠️ 重要:请填写 apiclient_key.pem 的内容,不是 apiclient_cert.pem!
|
||
内容应该以 -----BEGIN PRIVATE KEY----- 或 -----BEGIN RSA PRIVATE KEY----- 开头
|
||
`}
|
||
>
|
||
<Input.TextArea
|
||
rows={4}
|
||
placeholder="
|
||
-----BEGIN PRIVATE KEY-----
|
||
...这里是私钥内容...
|
||
-----END PRIVATE KEY-----
|
||
"
|
||
disabled={!wechatEnabled}
|
||
/>
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="wechat_cert_serial_no"
|
||
label="商户证书序列号"
|
||
extra="从 apiclient_cert.pem 中提取的证书序列号"
|
||
>
|
||
<Input placeholder="商户证书序列号" size="large" disabled={!wechatEnabled} />
|
||
</Form.Item>
|
||
<Form.Item name="wechat_api_v3_key" label="API v3 密钥">
|
||
<Input.Password placeholder="API v3 密钥" size="large" disabled={!wechatEnabled} />
|
||
</Form.Item>
|
||
<Form.Item name="wechat_notify_url" label="回调地址">
|
||
<Input placeholder="https://yourdomain.com/api/payments/wechat/callback" size="large" disabled={!wechatEnabled} />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="wechat_public_key"
|
||
label="微信平台公钥(可选)"
|
||
>
|
||
<Input.TextArea
|
||
rows={3}
|
||
placeholder="微信支付平台公钥内容"
|
||
disabled={!wechatEnabled}
|
||
/>
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="wechat_public_key_id"
|
||
label="微信平台公钥ID(可选)"
|
||
>
|
||
<Input placeholder="平台公钥ID" size="large" disabled={!wechatEnabled} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 支付宝 */}
|
||
<div style={{ flex: '1 1 400px', minWidth: 400 }}>
|
||
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||
<div style={{
|
||
width: 44, height: 44, borderRadius: 10,
|
||
background: 'rgba(0,122,255,0.08)',
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
fontSize: 22, color: '#007aff',
|
||
}}><AlipayCircleOutlined /></div>
|
||
<div>
|
||
<Typography.Title level={5} style={{ margin: 0 }}>支付宝</Typography.Title>
|
||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>支付宝应用支付配置</Typography.Text>
|
||
</div>
|
||
</div>
|
||
<Switch checked={alipayEnabled} onChange={setAlipayEnabled} checkedChildren="已启用" unCheckedChildren="未启用" />
|
||
</div>
|
||
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item name="alipay_app_id" label="AppID">
|
||
<Input placeholder="支付宝应用AppID" size="large" disabled={!alipayEnabled} />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="alipay_private_key"
|
||
label="应用私钥"
|
||
extra="支付宝应用私钥(PKCS8格式),不需要添加头部和尾部标记"
|
||
>
|
||
<Input.TextArea rows={4} placeholder="
|
||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
|
||
" disabled={!alipayEnabled} />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="alipay_public_key"
|
||
label="支付宝公钥"
|
||
extra="从支付宝开放平台获取的支付宝公钥"
|
||
>
|
||
<Input.TextArea rows={4} placeholder="
|
||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||
" disabled={!alipayEnabled} />
|
||
</Form.Item>
|
||
<Form.Item name="alipay_gateway" label="网关地址">
|
||
<Input placeholder="https://openapi.alipay.com/gateway.do" size="large" disabled={!alipayEnabled} />
|
||
</Form.Item>
|
||
<Form.Item name="alipay_notify_url" label="回调地址">
|
||
<Input placeholder="https://yourdomain.com/api/payments/alipay/callback" size="large" disabled={!alipayEnabled} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 24 }}>
|
||
<Button type="primary" icon={<SaveOutlined />} onClick={handleSave} loading={saving}
|
||
size="large" style={{ borderRadius: 8, minWidth: 160, height: 44, fontSize: 15 }}>
|
||
保存配置
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AdminPaymentConfig;
|