mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 04:37:27 +00:00
Merge pull request #1823 from littlewrite/feat_subscribe_sp1
新增 creem 支付
This commit is contained in:
@@ -22,6 +22,7 @@ import { Card, Spin } from '@douyinfe/semi-ui';
|
||||
import SettingsGeneralPayment from '../../pages/Setting/Payment/SettingsGeneralPayment';
|
||||
import SettingsPaymentGateway from '../../pages/Setting/Payment/SettingsPaymentGateway';
|
||||
import SettingsPaymentGatewayStripe from '../../pages/Setting/Payment/SettingsPaymentGatewayStripe';
|
||||
import SettingsPaymentGatewayCreem from '../../pages/Setting/Payment/SettingsPaymentGatewayCreem';
|
||||
import { API, showError, toBoolean } from '../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -142,6 +143,9 @@ const PaymentSetting = () => {
|
||||
<Card style={{ marginTop: '10px' }}>
|
||||
<SettingsPaymentGatewayStripe options={inputs} refresh={onRefresh} />
|
||||
</Card>
|
||||
<Card style={{ marginTop: '10px' }}>
|
||||
<SettingsPaymentGatewayCreem options={inputs} refresh={onRefresh} />
|
||||
</Card>
|
||||
</Spin>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -52,6 +52,9 @@ const RechargeCard = ({
|
||||
t,
|
||||
enableOnlineTopUp,
|
||||
enableStripeTopUp,
|
||||
enableCreemTopUp,
|
||||
creemProducts,
|
||||
creemPreTopUp,
|
||||
presetAmounts,
|
||||
selectedPreset,
|
||||
selectPresetAmount,
|
||||
@@ -84,6 +87,7 @@ const RechargeCard = ({
|
||||
const onlineFormApiRef = useRef(null);
|
||||
const redeemFormApiRef = useRef(null);
|
||||
const showAmountSkeleton = useMinimumLoadingTime(amountLoading);
|
||||
console.log(' enabled screem ?', enableCreemTopUp, ' products ?', creemProducts);
|
||||
return (
|
||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||
{/* 卡片头部 */}
|
||||
@@ -216,7 +220,7 @@ const RechargeCard = ({
|
||||
<div className='py-8 flex justify-center'>
|
||||
<Spin size='large' />
|
||||
</div>
|
||||
) : enableOnlineTopUp || enableStripeTopUp ? (
|
||||
) : enableOnlineTopUp || enableStripeTopUp || enableCreemTopUp ? (
|
||||
<Form
|
||||
getFormApi={(api) => (onlineFormApiRef.current = api)}
|
||||
initValues={{ topUpCount: topUpCount }}
|
||||
@@ -480,6 +484,32 @@ const RechargeCard = ({
|
||||
</div>
|
||||
</Form.Slot>
|
||||
)}
|
||||
|
||||
{/* Creem 充值区域 */}
|
||||
{enableCreemTopUp && creemProducts.length > 0 && (
|
||||
<Form.Slot label={t('Creem 充值')}>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3'>
|
||||
{creemProducts.map((product, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
onClick={() => creemPreTopUp(product)}
|
||||
className='cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300'
|
||||
bodyStyle={{ textAlign: 'center', padding: '16px' }}
|
||||
>
|
||||
<div className='font-medium text-lg mb-2'>
|
||||
{product.name}
|
||||
</div>
|
||||
<div className='text-sm text-gray-600 mb-2'>
|
||||
{t('充值额度')}: {product.quota}
|
||||
</div>
|
||||
<div className='text-lg font-semibold text-blue-600'>
|
||||
{product.currency === 'EUR' ? '€' : '$'}{product.price}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Form.Slot>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
) : (
|
||||
|
||||
@@ -63,6 +63,12 @@ const TopUp = () => {
|
||||
);
|
||||
const [statusLoading, setStatusLoading] = useState(true);
|
||||
|
||||
// Creem 相关状态
|
||||
const [creemProducts, setCreemProducts] = useState([]);
|
||||
const [enableCreemTopUp, setEnableCreemTopUp] = useState(false);
|
||||
const [creemOpen, setCreemOpen] = useState(false);
|
||||
const [selectedCreemProduct, setSelectedCreemProduct] = useState(null);
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [payWay, setPayWay] = useState('');
|
||||
@@ -248,6 +254,55 @@ const TopUp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const creemPreTopUp = async (product) => {
|
||||
if (!enableCreemTopUp) {
|
||||
showError(t('管理员未开启 Creem 充值!'));
|
||||
return;
|
||||
}
|
||||
setSelectedCreemProduct(product);
|
||||
setCreemOpen(true);
|
||||
};
|
||||
|
||||
const onlineCreemTopUp = async () => {
|
||||
if (!selectedCreemProduct) {
|
||||
showError(t('请选择产品'));
|
||||
return;
|
||||
}
|
||||
// Validate product has required fields
|
||||
if (!selectedCreemProduct.productId) {
|
||||
showError(t('产品配置错误,请联系管理员'));
|
||||
return;
|
||||
}
|
||||
setConfirmLoading(true);
|
||||
try {
|
||||
const res = await API.post('/api/user/creem/pay', {
|
||||
product_id: selectedCreemProduct.productId,
|
||||
payment_method: 'creem',
|
||||
});
|
||||
if (res !== undefined) {
|
||||
const { message, data } = res.data;
|
||||
if (message === 'success') {
|
||||
processCreemCallback(data);
|
||||
} else {
|
||||
showError(data);
|
||||
}
|
||||
} else {
|
||||
showError(res);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
showError(t('支付请求失败'));
|
||||
} finally {
|
||||
setCreemOpen(false);
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const processCreemCallback = (data) => {
|
||||
// 与 Stripe 保持一致的实现方式
|
||||
window.open(data.checkout_url, '_blank');
|
||||
};
|
||||
|
||||
const getUserQuota = async () => {
|
||||
let res = await API.get(`/api/user/self`);
|
||||
const { success, message, data } = res.data;
|
||||
@@ -322,6 +377,7 @@ const TopUp = () => {
|
||||
setPayMethods(payMethods);
|
||||
const enableStripeTopUp = data.enable_stripe_topup || false;
|
||||
const enableOnlineTopUp = data.enable_online_topup || false;
|
||||
const enableCreemTopUp = data.enable_creem_topup || false;
|
||||
const minTopUpValue = enableOnlineTopUp
|
||||
? data.min_topup
|
||||
: enableStripeTopUp
|
||||
@@ -329,9 +385,20 @@ const TopUp = () => {
|
||||
: 1;
|
||||
setEnableOnlineTopUp(enableOnlineTopUp);
|
||||
setEnableStripeTopUp(enableStripeTopUp);
|
||||
setEnableCreemTopUp(enableCreemTopUp);
|
||||
setMinTopUp(minTopUpValue);
|
||||
setTopUpCount(minTopUpValue);
|
||||
|
||||
// 设置 Creem 产品
|
||||
try {
|
||||
console.log(' data is ?', data);
|
||||
console.log(' creem products is ?', data.creem_products);
|
||||
const products = JSON.parse(data.creem_products || '[]');
|
||||
setCreemProducts(products);
|
||||
} catch (e) {
|
||||
setCreemProducts([]);
|
||||
}
|
||||
|
||||
// 如果没有自定义充值数量选项,根据最小充值金额生成预设充值额度选项
|
||||
if (topupInfo.amount_options.length === 0) {
|
||||
setPresetAmounts(generatePresetAmounts(minTopUpValue));
|
||||
@@ -500,6 +567,11 @@ const TopUp = () => {
|
||||
setOpenHistory(false);
|
||||
};
|
||||
|
||||
const handleCreemCancel = () => {
|
||||
setCreemOpen(false);
|
||||
setSelectedCreemProduct(null);
|
||||
};
|
||||
|
||||
// 选择预设充值额度
|
||||
const selectPresetAmount = (preset) => {
|
||||
setTopUpCount(preset.value);
|
||||
@@ -563,6 +635,33 @@ const TopUp = () => {
|
||||
t={t}
|
||||
/>
|
||||
|
||||
{/* Creem 充值确认模态框 */}
|
||||
<Modal
|
||||
title={t('确定要充值 $')}
|
||||
visible={creemOpen}
|
||||
onOk={onlineCreemTopUp}
|
||||
onCancel={handleCreemCancel}
|
||||
maskClosable={false}
|
||||
size='small'
|
||||
centered
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
{selectedCreemProduct && (
|
||||
<>
|
||||
<p>
|
||||
{t('产品名称')}:{selectedCreemProduct.name}
|
||||
</p>
|
||||
<p>
|
||||
{t('价格')}:{selectedCreemProduct.currency === 'EUR' ? '€' : '$'}{selectedCreemProduct.price}
|
||||
</p>
|
||||
<p>
|
||||
{t('充值额度')}:{selectedCreemProduct.quota}
|
||||
</p>
|
||||
<p>{t('是否确认充值?')}</p>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* 用户信息头部 */}
|
||||
<div className='space-y-6'>
|
||||
<div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
|
||||
@@ -572,6 +671,9 @@ const TopUp = () => {
|
||||
t={t}
|
||||
enableOnlineTopUp={enableOnlineTopUp}
|
||||
enableStripeTopUp={enableStripeTopUp}
|
||||
enableCreemTopUp={enableCreemTopUp}
|
||||
creemProducts={creemProducts}
|
||||
creemPreTopUp={creemPreTopUp}
|
||||
presetAmounts={presetAmounts}
|
||||
selectedPreset={selectedPreset}
|
||||
selectPresetAmount={selectPresetAmount}
|
||||
|
||||
@@ -2071,6 +2071,35 @@
|
||||
"默认区域,如: us-central1": "Default region, e.g.: us-central1",
|
||||
"默认折叠侧边栏": "Default collapse sidebar",
|
||||
"默认测试模型": "Default Test Model",
|
||||
"默认补全倍率": "Default completion ratio"
|
||||
"默认补全倍率": "Default completion ratio",
|
||||
"选择充值套餐": "Choose a top-up package",
|
||||
"Creem 设置": "Creem Setting",
|
||||
"Creem 充值": "Creem Recharge",
|
||||
"Creem 介绍": "Creem is the payment partner you always deserved, we strive for simplicity and straightforwardness on our APIs.",
|
||||
"Creem Setting Tips": "Creem only supports preset fixed-amount products. These products and their prices need to be created and configured in advance on the Creem website, so custom dynamic amount top-ups are not supported. Configure the product name and price on Creem, obtain the Product Id, and then fill it in for the product below. Set the top-up amount and display price for this product in the new API.",
|
||||
"Webhook 密钥": "Webhook Secret",
|
||||
"测试模式": "Test Mode",
|
||||
"Creem API 密钥,敏感信息不显示": "Creem API key, sensitive information not displayed",
|
||||
"用于验证回调 new-api 的 webhook 请求的密钥,敏感信息不显示": "The key used to validate webhook requests for the callback new-api, sensitive information is not displayed.",
|
||||
"启用后将使用 Creem Test Mode": "",
|
||||
"展示价格": "Display Pricing",
|
||||
"Recharge Quota": "Recharge Quota",
|
||||
"产品配置": "Product Configuration",
|
||||
"产品名称": "Product Name",
|
||||
"产品ID": "Product ID",
|
||||
"暂无产品配置": "No product configuration",
|
||||
"更新 Creem 设置": "Update Creem Settings",
|
||||
"编辑产品": "Edit Product",
|
||||
"添加产品": "Add Product",
|
||||
"例如:基础套餐": "e.g.: Basic Package",
|
||||
"例如:prod_6I8rBerHpPxyoiU9WK4kot": "e.g.: prod_6I8rBerHpPxyoiU9WK4kot",
|
||||
"货币": "Currency",
|
||||
"欧元": "EUR",
|
||||
"USD (美元)": "USD (US Dollar)",
|
||||
"EUR (欧元)": "EUR (Euro)",
|
||||
"例如:4.99": "e.g.: 4.99",
|
||||
"例如:100000": "e.g.: 100000",
|
||||
"请填写完整的产品信息": "Please fill in complete product information",
|
||||
"产品ID已存在": "Product ID already exists"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2062,6 +2062,8 @@
|
||||
"默认区域,如: us-central1": "默认区域,如: us-central1",
|
||||
"默认折叠侧边栏": "默认折叠侧边栏",
|
||||
"默认测试模型": "默认测试模型",
|
||||
"默认补全倍率": "默认补全倍率"
|
||||
"默认补全倍率": "默认补全倍率",
|
||||
"Creem 介绍": "Creem 是一个简单的支付处理平台,支持固定金额产品销售,以及订阅销售。",
|
||||
"Creem Setting Tips": "Creem 只支持预设的固定金额产品,这产品以及价格需要提前在Creem网站内创建配置,所以不支持自定义动态金额充值。在Creem端配置产品的名字以及价格,获取Product Id 后填到下面的产品,在new-api为该产品设置充值额度,以及展示价格。"
|
||||
}
|
||||
}
|
||||
}
|
||||
385
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js
Normal file
385
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js
Normal file
@@ -0,0 +1,385 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import {
|
||||
Banner,
|
||||
Button,
|
||||
Form,
|
||||
Row,
|
||||
Col,
|
||||
Typography,
|
||||
Spin,
|
||||
Table,
|
||||
Modal,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
} from '@douyinfe/semi-ui';
|
||||
const { Text } = Typography;
|
||||
import {
|
||||
API,
|
||||
showError,
|
||||
showSuccess,
|
||||
} from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
|
||||
export default function SettingsPaymentGatewayCreem(props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [inputs, setInputs] = useState({
|
||||
CreemApiKey: '',
|
||||
CreemWebhookSecret: '',
|
||||
CreemProducts: '[]',
|
||||
CreemTestMode: false,
|
||||
});
|
||||
const [originInputs, setOriginInputs] = useState({});
|
||||
const [products, setProducts] = useState([]);
|
||||
const [showProductModal, setShowProductModal] = useState(false);
|
||||
const [editingProduct, setEditingProduct] = useState(null);
|
||||
const [productForm, setProductForm] = useState({
|
||||
name: '',
|
||||
productId: '',
|
||||
price: 0,
|
||||
quota: 0,
|
||||
currency: 'USD',
|
||||
});
|
||||
const formApiRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.options && formApiRef.current) {
|
||||
const currentInputs = {
|
||||
CreemApiKey: props.options.CreemApiKey || '',
|
||||
CreemWebhookSecret: props.options.CreemWebhookSecret || '',
|
||||
CreemProducts: props.options.CreemProducts || '[]',
|
||||
CreemTestMode: props.options.CreemTestMode === 'true',
|
||||
};
|
||||
setInputs(currentInputs);
|
||||
setOriginInputs({ ...currentInputs });
|
||||
formApiRef.current.setValues(currentInputs);
|
||||
|
||||
// Parse products
|
||||
try {
|
||||
const parsedProducts = JSON.parse(currentInputs.CreemProducts);
|
||||
setProducts(parsedProducts);
|
||||
} catch (e) {
|
||||
setProducts([]);
|
||||
}
|
||||
}
|
||||
}, [props.options]);
|
||||
|
||||
const handleFormChange = (values) => {
|
||||
setInputs(values);
|
||||
};
|
||||
|
||||
const submitCreemSetting = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const options = [];
|
||||
|
||||
if (inputs.CreemApiKey && inputs.CreemApiKey !== '') {
|
||||
options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey });
|
||||
}
|
||||
|
||||
if (inputs.CreemWebhookSecret && inputs.CreemWebhookSecret !== '') {
|
||||
options.push({ key: 'CreemWebhookSecret', value: inputs.CreemWebhookSecret });
|
||||
}
|
||||
|
||||
// Save test mode setting
|
||||
options.push({ key: 'CreemTestMode', value: inputs.CreemTestMode ? 'true' : 'false' });
|
||||
|
||||
// Save products as JSON string
|
||||
options.push({ key: 'CreemProducts', value: JSON.stringify(products) });
|
||||
|
||||
// 发送请求
|
||||
const requestQueue = options.map(opt =>
|
||||
API.put('/api/option/', {
|
||||
key: opt.key,
|
||||
value: opt.value,
|
||||
})
|
||||
);
|
||||
|
||||
const results = await Promise.all(requestQueue);
|
||||
|
||||
// 检查所有请求是否成功
|
||||
const errorResults = results.filter(res => !res.data.success);
|
||||
if (errorResults.length > 0) {
|
||||
errorResults.forEach(res => {
|
||||
showError(res.data.message);
|
||||
});
|
||||
} else {
|
||||
showSuccess(t('更新成功'));
|
||||
// 更新本地存储的原始值
|
||||
setOriginInputs({ ...inputs });
|
||||
props.refresh?.();
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('更新失败'));
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const openProductModal = (product = null) => {
|
||||
if (product) {
|
||||
setEditingProduct(product);
|
||||
setProductForm({ ...product });
|
||||
} else {
|
||||
setEditingProduct(null);
|
||||
setProductForm({
|
||||
name: '',
|
||||
productId: '',
|
||||
price: 0,
|
||||
quota: 0,
|
||||
currency: 'USD',
|
||||
});
|
||||
}
|
||||
setShowProductModal(true);
|
||||
};
|
||||
|
||||
const closeProductModal = () => {
|
||||
setShowProductModal(false);
|
||||
setEditingProduct(null);
|
||||
setProductForm({
|
||||
name: '',
|
||||
productId: '',
|
||||
price: 0,
|
||||
quota: 0,
|
||||
currency: 'USD',
|
||||
});
|
||||
};
|
||||
|
||||
const saveProduct = () => {
|
||||
if (!productForm.name || !productForm.productId || productForm.price <= 0 || productForm.quota <= 0 || !productForm.currency) {
|
||||
showError(t('请填写完整的产品信息'));
|
||||
return;
|
||||
}
|
||||
|
||||
let newProducts = [...products];
|
||||
if (editingProduct) {
|
||||
// 编辑现有产品
|
||||
const index = newProducts.findIndex(p => p.productId === editingProduct.productId);
|
||||
if (index !== -1) {
|
||||
newProducts[index] = { ...productForm };
|
||||
}
|
||||
} else {
|
||||
// 添加新产品
|
||||
if (newProducts.find(p => p.productId === productForm.productId)) {
|
||||
showError(t('产品ID已存在'));
|
||||
return;
|
||||
}
|
||||
newProducts.push({ ...productForm });
|
||||
}
|
||||
|
||||
setProducts(newProducts);
|
||||
closeProductModal();
|
||||
};
|
||||
|
||||
const deleteProduct = (productId) => {
|
||||
const newProducts = products.filter(p => p.productId !== productId);
|
||||
setProducts(newProducts);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('产品名称'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: t('产品ID'),
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
},
|
||||
{
|
||||
title: t('展示价格'),
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
render: (price, record) => `${record.currency === 'EUR' ? '€' : '$'}${price}`,
|
||||
},
|
||||
{
|
||||
title: t('充值额度'),
|
||||
dataIndex: 'quota',
|
||||
key: 'quota',
|
||||
},
|
||||
{
|
||||
title: t('操作'),
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='tertiary'
|
||||
size='small'
|
||||
onClick={() => openProductModal(record)}
|
||||
>
|
||||
{t('编辑')}
|
||||
</Button>
|
||||
<Button
|
||||
type='danger'
|
||||
theme='borderless'
|
||||
size='small'
|
||||
icon={<Trash2 size={14} />}
|
||||
onClick={() => deleteProduct(record.productId)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
initValues={inputs}
|
||||
onValueChange={handleFormChange}
|
||||
getFormApi={(api) => (formApiRef.current = api)}
|
||||
>
|
||||
<Form.Section text={t('Creem 设置')}>
|
||||
<Text>
|
||||
{t('Creem 介绍')}
|
||||
<a
|
||||
href='https://creem.io'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>Creem Official Site</a>
|
||||
<br />
|
||||
</Text>
|
||||
<Banner
|
||||
type='info'
|
||||
description={t('Creem Setting Tips')}
|
||||
/>
|
||||
|
||||
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}>
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
||||
<Form.Input
|
||||
field='CreemApiKey'
|
||||
label={t('API 密钥')}
|
||||
placeholder={t('Creem API 密钥,敏感信息不显示')}
|
||||
type='password'
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
||||
<Form.Input
|
||||
field='CreemWebhookSecret'
|
||||
label={t('Webhook 密钥')}
|
||||
placeholder={t('用于验证回调 new-api 的 webhook 请求的密钥,敏感信息不显示')}
|
||||
type='password'
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
||||
<Form.Switch
|
||||
field='CreemTestMode'
|
||||
label={t('测试模式')}
|
||||
extraText={t('启用后将使用 Creem Test Mode')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<Text strong>{t('产品配置')}</Text>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<Plus size={16} />}
|
||||
onClick={() => openProductModal()}
|
||||
>
|
||||
{t('添加产品')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={products}
|
||||
pagination={false}
|
||||
empty={
|
||||
<div className='text-center py-8'>
|
||||
<Text type='tertiary'>{t('暂无产品配置')}</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button onClick={submitCreemSetting} style={{ marginTop: 16 }}>
|
||||
{t('更新 Creem 设置')}
|
||||
</Button>
|
||||
</Form.Section>
|
||||
</Form>
|
||||
|
||||
{/* 产品配置模态框 */}
|
||||
<Modal
|
||||
title={editingProduct ? t('编辑产品') : t('添加产品')}
|
||||
visible={showProductModal}
|
||||
onOk={saveProduct}
|
||||
onCancel={closeProductModal}
|
||||
maskClosable={false}
|
||||
size='small'
|
||||
centered
|
||||
>
|
||||
<div className='space-y-4'>
|
||||
<div>
|
||||
<Text strong className='block mb-2'>
|
||||
{t('产品名称')}
|
||||
</Text>
|
||||
<Input
|
||||
value={productForm.name}
|
||||
onChange={(value) => setProductForm({ ...productForm, name: value })}
|
||||
placeholder={t('例如:基础套餐')}
|
||||
size='large'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text strong className='block mb-2'>
|
||||
{t('产品ID')}
|
||||
</Text>
|
||||
<Input
|
||||
value={productForm.productId}
|
||||
onChange={(value) => setProductForm({ ...productForm, productId: value })}
|
||||
placeholder={t('例如:prod_6I8rBerHpPxyoiU9WK4kot')}
|
||||
size='large'
|
||||
disabled={!!editingProduct}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text strong className='block mb-2'>
|
||||
{t('货币')}
|
||||
</Text>
|
||||
<Select
|
||||
value={productForm.currency}
|
||||
onChange={(value) => setProductForm({ ...productForm, currency: value })}
|
||||
size='large'
|
||||
className='w-full'
|
||||
>
|
||||
<Select.Option value='USD'>{t('USD (美元)')}</Select.Option>
|
||||
<Select.Option value='EUR'>{t('EUR (欧元)')}</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Text strong className='block mb-2'>
|
||||
{t('价格')} ({productForm.currency === 'EUR' ? t('欧元') : t('美元')})
|
||||
</Text>
|
||||
<InputNumber
|
||||
value={productForm.price}
|
||||
onChange={(value) => setProductForm({ ...productForm, price: value })}
|
||||
placeholder={t('例如:4.99')}
|
||||
min={0.01}
|
||||
precision={2}
|
||||
size='large'
|
||||
className='w-full'
|
||||
defaultValue={4.49}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text strong className='block mb-2'>
|
||||
{t('充值额度')}
|
||||
</Text>
|
||||
<InputNumber
|
||||
value={productForm.quota}
|
||||
onChange={(value) => setProductForm({ ...productForm, quota: value })}
|
||||
placeholder={t('例如:100000')}
|
||||
min={1}
|
||||
precision={0}
|
||||
size='large'
|
||||
className='w-full'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user