Merge branch 'main' into feat_subscribe_sp1

This commit is contained in:
Little Write
2025-09-27 22:54:52 +08:00
617 changed files with 64415 additions and 27156 deletions

View File

@@ -1,9 +1,24 @@
/*
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 {
Button,
Form,
Spin,
} from '@douyinfe/semi-ui';
import { Button, Form, Spin } from '@douyinfe/semi-ui';
import {
API,
removeTrailingSlash,
@@ -22,7 +37,9 @@ export default function SettingsGeneralPayment(props) {
useEffect(() => {
if (props.options && formApiRef.current) {
const currentInputs = { ServerAddress: props.options.ServerAddress || '' };
const currentInputs = {
ServerAddress: props.options.ServerAddress || '',
};
setInputs(currentInputs);
formApiRef.current.setValues(currentInputs);
}
@@ -65,11 +82,13 @@ export default function SettingsGeneralPayment(props) {
label={t('服务器地址')}
placeholder={'https://yourdomain.com'}
style={{ width: '100%' }}
extraText={t('该服务器地址将影响支付回调地址以及默认首页展示的地址,请确保正确配置')}
extraText={t(
'该服务器地址将影响支付回调地址以及默认首页展示的地址,请确保正确配置',
)}
/>
<Button onClick={submitServerAddress}>{t('更新服务器地址')}</Button>
</Form.Section>
</Form>
</Spin>
);
}
}

View File

@@ -1,12 +1,24 @@
/*
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 {
Button,
Form,
Row,
Col,
Typography,
Spin,
} from '@douyinfe/semi-ui';
import { Button, Form, Row, Col, Typography, Spin } from '@douyinfe/semi-ui';
const { Text } = Typography;
import {
API,
@@ -29,6 +41,8 @@ export default function SettingsPaymentGateway(props) {
TopupGroupRatio: '',
CustomCallbackAddress: '',
PayMethods: '',
AmountOptions: '',
AmountDiscount: '',
});
const [originInputs, setOriginInputs] = useState({});
const formApiRef = useRef(null);
@@ -39,12 +53,41 @@ export default function SettingsPaymentGateway(props) {
PayAddress: props.options.PayAddress || '',
EpayId: props.options.EpayId || '',
EpayKey: props.options.EpayKey || '',
Price: props.options.Price !== undefined ? parseFloat(props.options.Price) : 7.3,
MinTopUp: props.options.MinTopUp !== undefined ? parseFloat(props.options.MinTopUp) : 1,
Price:
props.options.Price !== undefined
? parseFloat(props.options.Price)
: 7.3,
MinTopUp:
props.options.MinTopUp !== undefined
? parseFloat(props.options.MinTopUp)
: 1,
TopupGroupRatio: props.options.TopupGroupRatio || '',
CustomCallbackAddress: props.options.CustomCallbackAddress || '',
PayMethods: props.options.PayMethods || '',
AmountOptions: props.options.AmountOptions || '',
AmountDiscount: props.options.AmountDiscount || '',
};
// JSON
try {
if (currentInputs.AmountOptions) {
currentInputs.AmountOptions = JSON.stringify(
JSON.parse(currentInputs.AmountOptions),
null,
2,
);
}
} catch {}
try {
if (currentInputs.AmountDiscount) {
currentInputs.AmountDiscount = JSON.stringify(
JSON.parse(currentInputs.AmountDiscount),
null,
2,
);
}
} catch {}
setInputs(currentInputs);
setOriginInputs({ ...currentInputs });
formApiRef.current.setValues(currentInputs);
@@ -75,6 +118,20 @@ export default function SettingsPaymentGateway(props) {
}
}
if (originInputs['AmountOptions'] !== inputs.AmountOptions && inputs.AmountOptions.trim() !== '') {
if (!verifyJSON(inputs.AmountOptions)) {
showError(t('自定义充值数量选项不是合法的 JSON 数组'));
return;
}
}
if (originInputs['AmountDiscount'] !== inputs.AmountDiscount && inputs.AmountDiscount.trim() !== '') {
if (!verifyJSON(inputs.AmountDiscount)) {
showError(t('充值金额折扣配置不是合法的 JSON 对象'));
return;
}
}
setLoading(true);
try {
const options = [
@@ -105,21 +162,27 @@ export default function SettingsPaymentGateway(props) {
if (originInputs['PayMethods'] !== inputs.PayMethods) {
options.push({ key: 'PayMethods', value: inputs.PayMethods });
}
if (originInputs['AmountOptions'] !== inputs.AmountOptions) {
options.push({ key: 'payment_setting.amount_options', value: inputs.AmountOptions });
}
if (originInputs['AmountDiscount'] !== inputs.AmountDiscount) {
options.push({ key: 'payment_setting.amount_discount', value: inputs.AmountDiscount });
}
//
const requestQueue = options.map(opt =>
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);
const errorResults = results.filter((res) => !res.data.success);
if (errorResults.length > 0) {
errorResults.forEach(res => {
errorResults.forEach((res) => {
showError(res.data.message);
});
} else {
@@ -143,11 +206,11 @@ export default function SettingsPaymentGateway(props) {
>
<Form.Section text={t('支付设置')}>
<Text>
{t('(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)')}
{t(
'(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)',
)}
</Text>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
>
<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='PayAddress'
@@ -210,9 +273,40 @@ export default function SettingsPaymentGateway(props) {
placeholder={t('为一个 JSON 文本')}
autosize
/>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 16 }}
>
<Col span={24}>
<Form.TextArea
field='AmountOptions'
label={t('自定义充值数量选项')}
placeholder={t('为一个 JSON 数组,例如:[10, 20, 50, 100, 200, 500]')}
autosize
extraText={t('设置用户可选择的充值数量选项,例如:[10, 20, 50, 100, 200, 500]')}
/>
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 16 }}
>
<Col span={24}>
<Form.TextArea
field='AmountDiscount'
label={t('充值金额折扣配置')}
placeholder={t('为一个 JSON 对象,例如:{"100": 0.95, "200": 0.9, "500": 0.85}')}
autosize
extraText={t('设置不同充值金额对应的折扣,键为充值金额,值为折扣率,例如:{"100": 0.95, "200": 0.9, "500": 0.85}')}
/>
</Col>
</Row>
<Button onClick={submitPayAddress}>{t('更新支付设置')}</Button>
</Form.Section>
</Form>
</Spin>
);
}
}

View File

@@ -1,3 +1,22 @@
/*
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,
@@ -36,8 +55,14 @@ export default function SettingsPaymentGateway(props) {
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,
StripeUnitPrice:
props.options.StripeUnitPrice !== undefined
? parseFloat(props.options.StripeUnitPrice)
: 8.0,
StripeMinTopUp:
props.options.StripeMinTopUp !== undefined
? parseFloat(props.options.StripeMinTopUp)
: 1,
};
setInputs(currentInputs);
setOriginInputs({ ...currentInputs });
@@ -57,38 +82,53 @@ export default function SettingsPaymentGateway(props) {
setLoading(true);
try {
const options = []
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 });
options.push({
key: 'StripeWebhookSecret',
value: inputs.StripeWebhookSecret,
});
}
if (inputs.StripePriceId !== '') {
options.push({key: 'StripePriceId', value: 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.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 (
inputs.StripeMinTopUp !== undefined &&
inputs.StripeMinTopUp !== null
) {
options.push({
key: 'StripeMinTopUp',
value: inputs.StripeMinTopUp.toString(),
});
}
//
const requestQueue = options.map(opt =>
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);
const errorResults = results.filter((res) => !res.data.success);
if (errorResults.length > 0) {
errorResults.forEach(res => {
errorResults.forEach((res) => {
showError(res.data.message);
});
} else {
@@ -114,40 +154,39 @@ export default function SettingsPaymentGateway(props) {
<Text>
Stripe 密钥Webhook 等设置请
<a
href='https://dashboard.stripe.com/developers'
target='_blank'
rel='noreferrer'
href='https://dashboard.stripe.com/developers'
target='_blank'
rel='noreferrer'
>
点击此处
</a>
进行设置最好先在
<a
href='https://dashboard.stripe.com/test/developers'
target='_blank'
rel='noreferrer'
href='https://dashboard.stripe.com/test/developers'
target='_blank'
rel='noreferrer'
>
测试环境
</a>
进行测试
<br />
</Text>
<Banner
type='info'
description={`Webhook 填:${props.options.ServerAddress ? removeTrailingSlash(props.options.ServerAddress) : t('网站地址')}/api/stripe/webhook`}
type='info'
description={`Webhook 填:${props.options.ServerAddress ? removeTrailingSlash(props.options.ServerAddress) : t('网站地址')}/api/stripe/webhook`}
/>
<Banner
type='warning'
description={`需要包含事件checkout.session.completed 和 checkout.session.expired`}
type='warning'
description={`需要包含事件checkout.session.completed 和 checkout.session.expired`}
/>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
>
<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 的 Stripe 密钥,敏感信息不显示')}
placeholder={t(
'sk_xxx 或 rk_xxx 的 Stripe 密钥,敏感信息不显示',
)}
type='password'
/>
</Col>
@@ -192,4 +231,4 @@ export default function SettingsPaymentGateway(props) {
</Form>
</Spin>
);
}
}