Files
new-api/web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx
T
2026-04-18 14:22:54 +08:00

267 lines
8.9 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useState, useRef } from 'react';
import { Banner, Button, Form, Row, Col, Spin } from '@douyinfe/semi-ui';
import {
API,
removeTrailingSlash,
showError,
showSuccess,
} from '../../../helpers';
import { useTranslation } from 'react-i18next';
import { BookOpen, TriangleAlert } from 'lucide-react';
export default function SettingsPaymentGateway(props) {
const { t } = useTranslation();
const sectionTitle = props.hideSectionTitle ? undefined : t('Stripe 设置');
const [loading, setLoading] = useState(false);
const [inputs, setInputs] = useState({
StripeApiSecret: '',
StripeWebhookSecret: '',
StripePriceId: '',
StripeUnitPrice: 8.0,
StripeMinTopUp: 1,
StripePromotionCodesEnabled: false,
});
const [originInputs, setOriginInputs] = useState({});
const formApiRef = useRef(null);
useEffect(() => {
if (props.options && formApiRef.current) {
const currentInputs = {
StripeApiSecret: props.options.StripeApiSecret || '',
StripeWebhookSecret: props.options.StripeWebhookSecret || '',
StripePriceId: props.options.StripePriceId || '',
StripeUnitPrice:
props.options.StripeUnitPrice !== undefined
? parseFloat(props.options.StripeUnitPrice)
: 8.0,
StripeMinTopUp:
props.options.StripeMinTopUp !== undefined
? parseFloat(props.options.StripeMinTopUp)
: 1,
StripePromotionCodesEnabled:
props.options.StripePromotionCodesEnabled !== undefined
? props.options.StripePromotionCodesEnabled
: false,
};
setInputs(currentInputs);
setOriginInputs({ ...currentInputs });
formApiRef.current.setValues(currentInputs);
}
}, [props.options]);
const handleFormChange = (values) => {
setInputs(values);
};
const submitStripeSetting = async () => {
if (props.options.ServerAddress === '') {
showError(t('请先填写服务器地址'));
return;
}
setLoading(true);
try {
const options = [];
if (inputs.StripeApiSecret && inputs.StripeApiSecret !== '') {
options.push({ key: 'StripeApiSecret', value: inputs.StripeApiSecret });
}
if (inputs.StripeWebhookSecret && inputs.StripeWebhookSecret !== '') {
options.push({
key: 'StripeWebhookSecret',
value: inputs.StripeWebhookSecret,
});
}
if (inputs.StripePriceId !== '') {
options.push({ key: 'StripePriceId', value: inputs.StripePriceId });
}
if (
inputs.StripeUnitPrice !== undefined &&
inputs.StripeUnitPrice !== null
) {
options.push({
key: 'StripeUnitPrice',
value: inputs.StripeUnitPrice.toString(),
});
}
if (
inputs.StripeMinTopUp !== undefined &&
inputs.StripeMinTopUp !== null
) {
options.push({
key: 'StripeMinTopUp',
value: inputs.StripeMinTopUp.toString(),
});
}
if (
originInputs['StripePromotionCodesEnabled'] !==
inputs.StripePromotionCodesEnabled &&
inputs.StripePromotionCodesEnabled !== undefined
) {
options.push({
key: 'StripePromotionCodesEnabled',
value: inputs.StripePromotionCodesEnabled ? 'true' : 'false',
});
}
// 发送请求
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);
};
return (
<Spin spinning={loading}>
<Form
initValues={inputs}
onValueChange={handleFormChange}
getFormApi={(api) => (formApiRef.current = api)}
>
<Form.Section text={sectionTitle}>
<Banner
type='info'
icon={<BookOpen size={16} />}
description={
<>
Stripe 密钥Webhook 等设置请
<a
href='https://dashboard.stripe.com/developers'
target='_blank'
rel='noreferrer'
>
点击此处
</a>
进行设置建议先在
<a
href='https://dashboard.stripe.com/test/developers'
target='_blank'
rel='noreferrer'
>
测试环境
</a>
完成联调
<br />
{t('回调地址')}
{props.options.ServerAddress
? removeTrailingSlash(props.options.ServerAddress)
: t('网站地址')}
/api/stripe/webhook
</>
}
style={{ marginBottom: 12 }}
/>
<Banner
type='warning'
icon={<TriangleAlert size={16} />}
description='需要包含事件:checkout.session.completed 和 checkout.session.expired'
style={{ marginBottom: 16 }}
/>
<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='StripeApiSecret'
label={t('API 密钥')}
placeholder={t('例如:sk_xxx 或 rk_xxx,留空表示保持当前不变')}
extraText={t(
'保存后不会回显,请填写当前环境对应的 Stripe API 密钥',
)}
type='password'
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input
field='StripeWebhookSecret'
label={t('Webhook 签名密钥')}
placeholder={t('例如:whsec_xxx,留空表示保持当前不变')}
extraText={t('用于校验 Stripe Webhook 签名,保存后不会回显')}
type='password'
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input
field='StripePriceId'
label={t('商品价格 ID')}
placeholder={t('例如:price_xxx')}
extraText={t('在 Stripe 后台创建价格后获得')}
/>
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 16 }}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.InputNumber
field='StripeUnitPrice'
precision={2}
label={t('充值价格(x元/美金)')}
placeholder={t('例如:7,就是7元/美金')}
extraText={t('按 1 美元对应的站内价格填写')}
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.InputNumber
field='StripeMinTopUp'
label={t('最低充值美元数量')}
placeholder={t('例如:2,就是最低充值2$')}
extraText={t('用户单次最少可充值的美元数量')}
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Switch
field='StripePromotionCodesEnabled'
size='default'
checkedText=''
uncheckedText=''
label={t('允许在 Stripe 支付中输入促销码')}
/>
</Col>
</Row>
<Button onClick={submitStripeSetting}>{t('更新 Stripe 设置')}</Button>
</Form.Section>
</Form>
</Spin>
);
}