fix(payment): resolve payment settings save logic and enable persistence

- Fix Bug 1: Always save all provider-specific fields regardless of
  currently selected provider in dropdown, preventing data loss when
  switching between Alipay/WeChat/ePay/Yipay configurations
- Fix Bug 2: Add EpayEnabled/YipayEnabled backend Go variables and
  OptionMap entries so enable/disable checkboxes persist across page
  reloads
- Fix white screen crash by wrapping orphan Col elements in proper Row
- Add EpayEnabled/YipayEnabled to PaymentSetting.jsx initial state
- Remove duplicate payment card imports for Stripe/Creem/Waffo/Alipay/Wechat
This commit is contained in:
xiezhouwei 2026-06-09 14:35:37 +08:00
parent 6fc114ec32
commit 82016579db
8 changed files with 277 additions and 286 deletions

1
$null Normal file
View File

@ -0,0 +1 @@
ERROR: The process "main.exe" not found.

View File

@ -102,6 +102,8 @@ func InitOptionMap() {
common.OptionMap["YipayReturnUrl"] = operation_setting.YipayReturnUrl common.OptionMap["YipayReturnUrl"] = operation_setting.YipayReturnUrl
common.OptionMap["YipayRequestURL"] = operation_setting.YipayRequestURL common.OptionMap["YipayRequestURL"] = operation_setting.YipayRequestURL
common.OptionMap["YipayChannelExtra"] = operation_setting.YipayChannelExtra common.OptionMap["YipayChannelExtra"] = operation_setting.YipayChannelExtra
common.OptionMap["EpayEnabled"] = strconv.FormatBool(operation_setting.EpayEnabled)
common.OptionMap["YipayEnabled"] = strconv.FormatBool(operation_setting.YipayEnabled)
common.OptionMap["Price"] = strconv.FormatFloat(operation_setting.Price, 'f', -1, 64) common.OptionMap["Price"] = strconv.FormatFloat(operation_setting.Price, 'f', -1, 64)
common.OptionMap["USDExchangeRate"] = strconv.FormatFloat(operation_setting.USDExchangeRate, 'f', -1, 64) common.OptionMap["USDExchangeRate"] = strconv.FormatFloat(operation_setting.USDExchangeRate, 'f', -1, 64)
common.OptionMap["MinTopUp"] = strconv.Itoa(operation_setting.MinTopUp) common.OptionMap["MinTopUp"] = strconv.Itoa(operation_setting.MinTopUp)
@ -495,6 +497,10 @@ func updateOptionMap(key string, value string) (err error) {
case "YipayChannelExtra": case "YipayChannelExtra":
operation_setting.YipayChannelExtra = strings.TrimSpace(value) operation_setting.YipayChannelExtra = strings.TrimSpace(value)
common.OptionMap[key] = operation_setting.YipayChannelExtra common.OptionMap[key] = operation_setting.YipayChannelExtra
case "EpayEnabled":
operation_setting.EpayEnabled = value == "true"
case "YipayEnabled":
operation_setting.YipayEnabled = value == "true"
case "Price": case "Price":
operation_setting.Price, _ = strconv.ParseFloat(value, 64) operation_setting.Price, _ = strconv.ParseFloat(value, 64)
case "USDExchangeRate": case "USDExchangeRate":

View File

@ -8,6 +8,8 @@ var CustomCallbackAddress = ""
var EpayId = "" var EpayId = ""
var EpayKey = "" var EpayKey = ""
var YipayAppSecret = "" var YipayAppSecret = ""
var EpayEnabled = false
var YipayEnabled = false
var OnlinePayProvider = "yipay" var OnlinePayProvider = "yipay"
var Price = 7.3 var Price = 7.3
var MinTopUp = 1 var MinTopUp = 1

View File

@ -21,11 +21,7 @@ import React, { useEffect, useState } from "react";
import { Card, Spin } from "@douyinfe/semi-ui"; import { Card, Spin } from "@douyinfe/semi-ui";
import SettingsGeneralPayment from "../../pages/Setting/Payment/SettingsGeneralPayment"; import SettingsGeneralPayment from "../../pages/Setting/Payment/SettingsGeneralPayment";
import SettingsPaymentGateway from "../../pages/Setting/Payment/SettingsPaymentGateway"; import SettingsPaymentGateway from "../../pages/Setting/Payment/SettingsPaymentGateway";
import SettingsPaymentGatewayStripe from "../../pages/Setting/Payment/SettingsPaymentGatewayStripe";
import SettingsPaymentGatewayCreem from "../../pages/Setting/Payment/SettingsPaymentGatewayCreem";
import SettingsPaymentGatewayWaffo from "../../pages/Setting/Payment/SettingsPaymentGatewayWaffo";
import SettingsPaymentGatewayAlipay from "../../pages/Setting/Payment/SettingsPaymentGatewayAlipay";
import SettingsPaymentGatewayWechat from "../../pages/Setting/Payment/SettingsPaymentGatewayWechat";
import { API, showError, toBoolean } from "../../helpers"; import { API, showError, toBoolean } from "../../helpers";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -45,6 +41,8 @@ const PaymentSetting = () => {
YipayReturnUrl: "", YipayReturnUrl: "",
YipayChannelExtra: "", YipayChannelExtra: "",
YipayRequestURL: "", YipayRequestURL: "",
EpayEnabled: false,
YipayEnabled: false,
Price: 7.3, Price: 7.3,
MinTopUp: 1, MinTopUp: 1,
TopupGroupRatio: "", TopupGroupRatio: "",
@ -53,13 +51,6 @@ const PaymentSetting = () => {
AmountOptions: "", AmountOptions: "",
AmountDiscount: "", AmountDiscount: "",
StripeApiSecret: "",
StripeWebhookSecret: "",
StripePriceId: "",
StripeUnitPrice: 8.0,
StripeMinTopUp: 1,
StripePromotionCodesEnabled: false,
AlipayEnabled: false, AlipayEnabled: false,
AlipayAppId: "", AlipayAppId: "",
AlipayPrivateKey: "", AlipayPrivateKey: "",
@ -131,10 +122,6 @@ const PaymentSetting = () => {
break; break;
case "Price": case "Price":
case "MinTopUp": case "MinTopUp":
case "StripeUnitPrice":
case "StripeMinTopUp":
newInputs[item.key] = parseFloat(item.value);
break;
case "YipayAppSecret": case "YipayAppSecret":
newInputs[item.key] = newInputs[item.key] =
item.value != null && item.value !== undefined item.value != null && item.value !== undefined
@ -181,21 +168,6 @@ const PaymentSetting = () => {
<Card style={{ marginTop: "10px" }}> <Card style={{ marginTop: "10px" }}>
<SettingsPaymentGateway options={inputs} refresh={onRefresh} /> <SettingsPaymentGateway options={inputs} refresh={onRefresh} />
</Card> </Card>
<Card style={{ marginTop: "10px" }}>
<SettingsPaymentGatewayStripe options={inputs} refresh={onRefresh} />
</Card>
<Card style={{ marginTop: "10px" }}>
<SettingsPaymentGatewayCreem options={inputs} refresh={onRefresh} />
</Card>
<Card style={{ marginTop: "10px" }}>
<SettingsPaymentGatewayWaffo options={inputs} refresh={onRefresh} />
</Card>
<Card style={{ marginTop: "10px" }}>
<SettingsPaymentGatewayAlipay options={inputs} refresh={onRefresh} />
</Card>
<Card style={{ marginTop: "10px" }}>
<SettingsPaymentGatewayWechat options={inputs} refresh={onRefresh} />
</Card>
</Spin> </Spin>
</> </>
); );

View File

@ -70,6 +70,8 @@ const RechargeCard = ({
statusLoading, statusLoading,
topupInfo, topupInfo,
onOpenHistory, onOpenHistory,
enableAlipayTopUp,
enableWechatPayTopUp,
enableWaffoTopUp, enableWaffoTopUp,
waffoTopUp, waffoTopUp,
waffoPayMethods, waffoPayMethods,
@ -213,13 +215,15 @@ const RechargeCard = ({
) : enableOnlineTopUp || ) : enableOnlineTopUp ||
enableStripeTopUp || enableStripeTopUp ||
enableCreemTopUp || enableCreemTopUp ||
enableAlipayTopUp ||
enableWechatPayTopUp ||
enableWaffoTopUp ? ( enableWaffoTopUp ? (
<Form <Form
getFormApi={(api) => (onlineFormApiRef.current = api)} getFormApi={(api) => (onlineFormApiRef.current = api)}
initValues={{ topUpCount: topUpCount }} initValues={{ topUpCount: topUpCount }}
> >
<div className='space-y-6'> <div className='space-y-6'>
{(enableOnlineTopUp || enableStripeTopUp || enableWaffoTopUp) && ( {(enableOnlineTopUp || enableStripeTopUp || enableWaffoTopUp || enableAlipayTopUp || enableWechatPayTopUp) && (
<Row gutter={12}> <Row gutter={12}>
<Col xs={24} sm={24} md={24} lg={10} xl={10}> <Col xs={24} sm={24} md={24} lg={10} xl={10}>
<Form.InputNumber <Form.InputNumber
@ -228,7 +232,9 @@ const RechargeCard = ({
disabled={ disabled={
!enableOnlineTopUp && !enableOnlineTopUp &&
!enableStripeTopUp && !enableStripeTopUp &&
!enableWaffoTopUp !enableWaffoTopUp &&
!enableAlipayTopUp &&
!enableWechatPayTopUp
} }
placeholder={ placeholder={
t('充值数量,最低 ') + renderTopUpCount(minTopUp) t('充值数量,最低 ') + renderTopUpCount(minTopUp)
@ -282,26 +288,30 @@ const RechargeCard = ({
/> />
</Col> </Col>
{payMethods && {payMethods &&
payMethods.filter((m) => m.type !== 'waffo').length > 0 && ( payMethods.filter((m) => m.type !== 'waffo' && m.type !== 'custom1').length > 0 && (
<Col xs={24} sm={24} md={24} lg={14} xl={14}> <Col xs={24} sm={24} md={24} lg={14} xl={14}>
<Form.Slot label={t('选择支付方式')}> <Form.Slot label={t('选择支付方式')}>
<Space wrap> <Space wrap>
{payMethods {payMethods
.filter((m) => m.type !== 'waffo') .filter((m) => m.type !== 'waffo' && m.type !== 'custom1')
.map((payMethod) => { .map((payMethod) => {
const minTopupVal = const minTopupVal =
Number(payMethod.min_topup) || 0; Number(payMethod.min_topup) || 0;
const isStripe = payMethod.type === 'stripe'; const isStripe = payMethod.type === 'stripe';
const isAlipay = payMethod.type === 'alipay';
const isWechat = payMethod.type === 'wxpay';
const disabled = const disabled =
(!enableOnlineTopUp && !isStripe) || (!enableOnlineTopUp && !isStripe && !isAlipay && !isWechat) ||
(!enableStripeTopUp && isStripe) || (!enableStripeTopUp && isStripe) ||
(!enableAlipayTopUp && isAlipay) ||
(!enableWechatPayTopUp && isWechat) ||
minTopupVal > Number(topUpCount || 0); minTopupVal > Number(topUpCount || 0);
const buttonEl = ( const buttonEl = (
<Button <Button
key={payMethod.type} key={payMethod.type}
theme='outline' theme={payWay === payMethod.type ? 'solid' : 'outline'}
type='tertiary' type={payWay === payMethod.type ? 'primary' : 'tertiary'}
onClick={() => preTopUp(payMethod.type)} onClick={() => preTopUp(payMethod.type)}
disabled={disabled} disabled={disabled}
loading={ loading={
@ -310,11 +320,11 @@ const RechargeCard = ({
} }
icon={ icon={
payMethod.type === 'alipay' ? ( payMethod.type === 'alipay' ? (
<SiAlipay size={18} color='#1677FF' /> <SiAlipay size={18} color={payWay === payMethod.type ? 'white' : '#1677FF'} />
) : payMethod.type === 'wxpay' ? ( ) : payMethod.type === 'wxpay' ? (
<SiWechat size={18} color='#07C160' /> <SiWechat size={18} color={payWay === payMethod.type ? 'white' : '#07C160'} />
) : payMethod.type === 'stripe' ? ( ) : payMethod.type === 'stripe' ? (
<SiStripe size={18} color='#635BFF' /> <SiStripe size={18} color={payWay === payMethod.type ? 'white' : '#635BFF'} />
) : ( ) : (
<CreditCard <CreditCard
size={18} size={18}
@ -356,7 +366,7 @@ const RechargeCard = ({
</Row> </Row>
)} )}
{(enableOnlineTopUp || enableStripeTopUp || enableWaffoTopUp) && ( {(enableOnlineTopUp || enableStripeTopUp || enableWaffoTopUp || enableAlipayTopUp || enableWechatPayTopUp) && (
<Form.Slot <Form.Slot
label={ label={
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>

View File

@ -527,22 +527,26 @@ const TopUp = () => {
const enableStripeTopUp = data.enable_stripe_topup || false; const enableStripeTopUp = data.enable_stripe_topup || false;
const enableOnlineTopUp = data.enable_online_topup || false; const enableOnlineTopUp = data.enable_online_topup || false;
const enableCreemTopUp = data.enable_creem_topup || false; const enableCreemTopUp = data.enable_creem_topup || false;
const enableWaffoTopUp = data.enable_waffo_topup || false;
const enableAlipayTopUp = data.enable_alipay_topup || false;
const enableWechatPayTopUp = data.enable_wechatpay_topup || false;
const minTopUpValue = enableOnlineTopUp const minTopUpValue = enableOnlineTopUp
? data.min_topup ? data.min_topup
: enableStripeTopUp : enableStripeTopUp
? data.stripe_min_topup ? data.stripe_min_topup
: data.enable_waffo_topup : enableWaffoTopUp
? data.waffo_min_topup ? data.waffo_min_topup
: 1; : enableAlipayTopUp
? data.alipay_min_topup
: enableWechatPayTopUp
? data.wechatpay_min_topup
: 1;
setEnableOnlineTopUp(enableOnlineTopUp); setEnableOnlineTopUp(enableOnlineTopUp);
setEnableStripeTopUp(enableStripeTopUp); setEnableStripeTopUp(enableStripeTopUp);
setEnableCreemTopUp(enableCreemTopUp); setEnableCreemTopUp(enableCreemTopUp);
const enableWaffoTopUp = data.enable_waffo_topup || false;
const enableAlipayTopUp = data.enable_alipay_topup || false;
setEnableAlipayTopUp(enableAlipayTopUp);
const enableWechatPayTopUp = data.enable_wechatpay_topup || false;
setEnableWechatPayTopUp(enableWechatPayTopUp);
setEnableWaffoTopUp(enableWaffoTopUp); setEnableWaffoTopUp(enableWaffoTopUp);
setEnableAlipayTopUp(enableAlipayTopUp);
setEnableWechatPayTopUp(enableWechatPayTopUp);
setWaffoPayMethods(data.waffo_pay_methods || []); setWaffoPayMethods(data.waffo_pay_methods || []);
setWaffoMinTopUp(data.waffo_min_topup || 1); setWaffoMinTopUp(data.waffo_min_topup || 1);
setMinTopUp(minTopUpValue); setMinTopUp(minTopUpValue);
@ -810,9 +814,13 @@ const TopUp = () => {
} }
const minTopupVal = Number(method.min_topup) || 0; const minTopupVal = Number(method.min_topup) || 0;
const isStripe = method.type === 'stripe'; const isStripe = method.type === 'stripe';
const isAlipay = method.type === 'alipay';
const isWechat = method.type === 'wxpay';
const disabled = const disabled =
(!enableOnlineTopUp && !isStripe) || (!enableOnlineTopUp && !isStripe && !isAlipay && !isWechat) ||
(!enableStripeTopUp && isStripe) || (!enableStripeTopUp && isStripe) ||
(!enableAlipayTopUp && isAlipay) ||
(!enableWechatPayTopUp && isWechat) ||
minTopupVal > Number(count || 0); minTopupVal > Number(count || 0);
return !disabled; return !disabled;
}); });
@ -823,7 +831,7 @@ const TopUp = () => {
selectPresetAmount(preset); selectPresetAmount(preset);
const enabledMethods = getEnabledEpayMethods(preset.value); const enabledMethods = getEnabledEpayMethods(preset.value);
if (enabledMethods.length === 0) { if (enabledMethods.length === 0) {
showError(t('当前额度暂无可用支付方式')); showError(t('暂无可用支付方式,请联系管理员配置'));
return; return;
} }
if (enabledMethods.length === 1) { if (enabledMethods.length === 1) {
@ -880,6 +888,8 @@ const TopUp = () => {
payMethods={payMethods} payMethods={payMethods}
enableOnlineTopUp={enableOnlineTopUp} enableOnlineTopUp={enableOnlineTopUp}
enableStripeTopUp={enableStripeTopUp} enableStripeTopUp={enableStripeTopUp}
enableAlipayTopUp={enableAlipayTopUp}
enableWechatPayTopUp={enableWechatPayTopUp}
onSelect={handlePaymentMethodSelect} onSelect={handlePaymentMethodSelect}
paymentLoading={paymentLoading} paymentLoading={paymentLoading}
payWay={payWay} payWay={payWay}
@ -994,6 +1004,8 @@ const TopUp = () => {
enableWaffoTopUp={enableWaffoTopUp} enableWaffoTopUp={enableWaffoTopUp}
waffoTopUp={waffoTopUp} waffoTopUp={waffoTopUp}
waffoPayMethods={waffoPayMethods} waffoPayMethods={waffoPayMethods}
enableAlipayTopUp={enableAlipayTopUp}
enableWechatPayTopUp={enableWechatPayTopUp}
presetAmounts={presetAmounts} presetAmounts={presetAmounts}
selectedPreset={selectedPreset} selectedPreset={selectedPreset}
selectPresetAmount={selectPresetAmount} selectPresetAmount={selectPresetAmount}

View File

@ -14,11 +14,13 @@ const PaymentMethodSelectModal = ({
payMethods = [], payMethods = [],
enableOnlineTopUp, enableOnlineTopUp,
enableStripeTopUp, enableStripeTopUp,
enableAlipayTopUp,
enableWechatPayTopUp,
onSelect, onSelect,
paymentLoading, paymentLoading,
payWay, payWay,
}) => { }) => {
const epayMethods = payMethods.filter((m) => m.type !== 'waffo'); const epayMethods = payMethods.filter((m) => m.type !== 'waffo' && m.type !== 'custom1');
const renderPayIcon = (payMethod) => { const renderPayIcon = (payMethod) => {
if (payMethod.type === 'alipay') { if (payMethod.type === 'alipay') {
@ -61,9 +63,13 @@ const PaymentMethodSelectModal = ({
{epayMethods.map((payMethod) => { {epayMethods.map((payMethod) => {
const minTopupVal = Number(payMethod.min_topup) || 0; const minTopupVal = Number(payMethod.min_topup) || 0;
const isStripe = payMethod.type === 'stripe'; const isStripe = payMethod.type === 'stripe';
const isAlipay = payMethod.type === 'alipay';
const isWechat = payMethod.type === 'wxpay';
const disabled = const disabled =
(!enableOnlineTopUp && !isStripe) || (!enableOnlineTopUp && !isStripe && !isAlipay && !isWechat) ||
(!enableStripeTopUp && isStripe) || (!enableStripeTopUp && isStripe) ||
(!enableAlipayTopUp && isAlipay) ||
(!enableWechatPayTopUp && isWechat) ||
minTopupVal > Number(topUpCount || 0); minTopupVal > Number(topUpCount || 0);
const buttonEl = ( const buttonEl = (

View File

@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/ */
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { Button, Form, Row, Col, Typography, Spin, TextArea } from '@douyinfe/semi-ui'; import { Button, Form, Row, Col, Typography, Spin, TextArea, Checkbox } from '@douyinfe/semi-ui';
const { Text } = Typography; const { Text } = Typography;
import { import {
API, API,
@ -67,6 +67,12 @@ function getDefaultYipayReturnHint(serverAddress) {
*/ */
function normalizeOnlinePayProvider(provider) { function normalizeOnlinePayProvider(provider) {
const value = (provider || '').toLowerCase(); const value = (provider || '').toLowerCase();
if (value === 'alipay') {
return 'alipay';
}
if (value === 'wechatpay' || value === 'wxpay') {
return 'wechatpay';
}
if (value === 'yipay' || value === 'jeepay') { if (value === 'yipay' || value === 'jeepay') {
return 'yipay'; return 'yipay';
} }
@ -96,6 +102,16 @@ function buildFormModelFromOptions(opts) {
YipayReturnUrl: opts.YipayReturnUrl || '', YipayReturnUrl: opts.YipayReturnUrl || '',
YipayChannelExtra: String(opts.YipayChannelExtra ?? ''), YipayChannelExtra: String(opts.YipayChannelExtra ?? ''),
YipayRequestURL: yipayReq, YipayRequestURL: yipayReq,
AlipayEnabled: opts.AlipayEnabled === true || opts.AlipayEnabled === 'true',
AlipayAppId: opts.AlipayAppId || '',
AlipayPrivateKey: opts.AlipayPrivateKey || '',
AlipayPublicKey: opts.AlipayPublicKey || '',
WechatPayEnabled: opts.WechatPayEnabled === true || opts.WechatPayEnabled === 'true',
WechatPayAppId: opts.WechatPayAppId || '',
WechatPayMchId: opts.WechatPayMchId || '',
WechatPayApiV3Key: opts.WechatPayApiV3Key || '',
EpayEnabled: opts.EpayEnabled === true || opts.EpayEnabled === 'true',
YipayEnabled: opts.YipayEnabled === true || opts.YipayEnabled === 'true',
Price: opts.Price !== undefined ? parseFloat(String(opts.Price)) : 7.3, Price: opts.Price !== undefined ? parseFloat(String(opts.Price)) : 7.3,
MinTopUp: MinTopUp:
opts.MinTopUp !== undefined ? parseFloat(String(opts.MinTopUp)) : 1, opts.MinTopUp !== undefined ? parseFloat(String(opts.MinTopUp)) : 1,
@ -149,6 +165,16 @@ export default function SettingsPaymentGateway(props) {
YipayReturnUrl: '', YipayReturnUrl: '',
YipayChannelExtra: '', YipayChannelExtra: '',
YipayRequestURL: '', YipayRequestURL: '',
AlipayEnabled: false,
AlipayAppId: '',
AlipayPrivateKey: '',
AlipayPublicKey: '',
WechatPayEnabled: false,
WechatPayAppId: '',
WechatPayMchId: '',
WechatPayApiV3Key: '',
EpayEnabled: false,
YipayEnabled: false,
Price: 7.3, Price: 7.3,
MinTopUp: 1, MinTopUp: 1,
TopupGroupRatio: '', TopupGroupRatio: '',
@ -289,6 +315,26 @@ export default function SettingsPaymentGateway(props) {
} }
} }
if (currentProvider === 'alipay') {
if (!inputs.AlipayAppId?.trim()) {
showError(t('支付宝模式下必须填写应用ID'));
return;
}
if (!inputs.AlipayPrivateKey?.trim()) {
showError(t('支付宝模式下必须填写私钥'));
return;
}
}
if (currentProvider === 'wechatpay') {
if (!inputs.WechatPayAppId?.trim()) {
showError(t('微信支付模式下必须填写应用ID'));
return;
}
if (!inputs.WechatPayMchId?.trim()) {
showError(t('微信支付模式下必须填写商户号'));
return;
}
}
if (currentProvider === 'yipay') { if (currentProvider === 'yipay') {
if (!inputs.YipayMchNo?.trim()) { if (!inputs.YipayMchNo?.trim()) {
showError(t('Yipay 模式下必须填写商户号')); showError(t('Yipay 模式下必须填写商户号'));
@ -328,50 +374,62 @@ export default function SettingsPaymentGateway(props) {
}, },
]; ];
if (currentProvider === 'epay') { // Alipay options
options.push({ options.push({ key: 'AlipayEnabled', value: inputs.AlipayEnabled ? 'true' : 'false' });
options.push({ key: 'AlipayAppId', value: inputs.AlipayAppId?.trim() || '' });
options.push({ key: 'AlipayPrivateKey', value: inputs.AlipayPrivateKey || '' });
options.push({ key: 'AlipayPublicKey', value: inputs.AlipayPublicKey?.trim() || '' });
// Epay/Yipay options
options.push({ key: 'EpayEnabled', value: inputs.EpayEnabled ? 'true' : 'false' });
options.push({ key: 'YipayEnabled', value: inputs.YipayEnabled ? 'true' : 'false' });
// WeChat Pay options
options.push({ key: 'WechatPayEnabled', value: inputs.WechatPayEnabled ? 'true' : 'false' });
options.push({ key: 'WechatPayAppId', value: inputs.WechatPayAppId?.trim() || '' });
options.push({ key: 'WechatPayMchId', value: inputs.WechatPayMchId?.trim() || '' });
options.push({ key: 'WechatPayApiV3Key', value: inputs.WechatPayApiV3Key || '' });
options.push({
key: 'PayAddress', key: 'PayAddress',
value: removeTrailingSlash(inputs.PayAddress || ''), value: removeTrailingSlash(inputs.PayAddress || ''),
}); });
}
if (currentProvider === 'yipay') {
const gatewayBase = removeTrailingSlash( const gatewayBase = removeTrailingSlash(
(inputs.YipayRequestURL || '').trim(), (inputs.YipayRequestURL || '').trim(),
); );
options.push({ key: 'YipayRequestURL', value: gatewayBase }); options.push({ key: 'YipayRequestURL', value: gatewayBase });
options.push({ key: 'PayAddress', value: gatewayBase }); options.push({ key: 'PayAddress', value: gatewayBase });
}
if (currentProvider === 'epay' && inputs.EpayId !== '') {
if (inputs.EpayId !== '') {
options.push({ key: 'EpayId', value: inputs.EpayId }); options.push({ key: 'EpayId', value: inputs.EpayId });
} }
if (currentProvider === 'yipay' && inputs.YipayMchNo !== '') { if (inputs.YipayMchNo !== '') {
options.push({ key: 'YipayMchNo', value: inputs.YipayMchNo.trim() }); options.push({ key: 'YipayMchNo', value: inputs.YipayMchNo.trim() });
} }
if ( if (
currentProvider === 'epay' &&
inputs.EpayKey !== undefined && inputs.EpayKey !== undefined &&
inputs.EpayKey !== '' inputs.EpayKey !== ''
) { ) {
options.push({ key: 'EpayKey', value: inputs.EpayKey }); options.push({ key: 'EpayKey', value: inputs.EpayKey });
} }
if (currentProvider === 'yipay' && inputs.YipayAppId !== '') { if (inputs.YipayAppId !== '') {
options.push({ key: 'YipayAppId', value: inputs.YipayAppId.trim() }); options.push({ key: 'YipayAppId', value: inputs.YipayAppId.trim() });
} }
if (currentProvider === 'yipay' && inputs.YipayNotifyUrl !== '') { if (inputs.YipayNotifyUrl !== '') {
options.push({ options.push({
key: 'YipayNotifyUrl', key: 'YipayNotifyUrl',
value: inputs.YipayNotifyUrl.trim(), value: inputs.YipayNotifyUrl.trim(),
}); });
} }
if (currentProvider === 'yipay' && inputs.YipayReturnUrl !== '') { if (inputs.YipayReturnUrl !== '') {
options.push({ options.push({
key: 'YipayReturnUrl', key: 'YipayReturnUrl',
value: inputs.YipayReturnUrl.trim(), value: inputs.YipayReturnUrl.trim(),
}); });
} }
if ( if (
currentProvider === 'yipay' &&
(inputs.YipayChannelExtra ?? '').trim() !== (inputs.YipayChannelExtra ?? '').trim() !==
(originInputs.YipayChannelExtra ?? '').trim() (originInputs.YipayChannelExtra ?? '').trim()
) { ) {
@ -381,7 +439,6 @@ export default function SettingsPaymentGateway(props) {
}); });
} }
if ( if (
currentProvider === 'yipay' &&
inputs.YipayAppSecret !== undefined && inputs.YipayAppSecret !== undefined &&
inputs.YipayAppSecret.trim() !== '' && inputs.YipayAppSecret.trim() !== '' &&
inputs.YipayAppSecret !== originInputs.YipayAppSecret inputs.YipayAppSecret !== originInputs.YipayAppSecret
@ -459,198 +516,161 @@ export default function SettingsPaymentGateway(props) {
> >
<Form.Section text={t('支付设置')}> <Form.Section text={t('支付设置')}>
<Text> <Text>
{t( {t('支持支付宝、微信支付、易支付 / Yipay 等在线支付渠道配置')}
'(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)',
)}
</Text> </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}> <Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Select <Form.Select
field='OnlinePayProvider' field="OnlinePayProvider"
label={t('在线支付通道')} label={t('在线支付通道')}
optionList={[ optionList={[
{ label: t('支付宝'), value: 'alipay' },
{ label: t('微信支付'), value: 'wechatpay' },
{ label: t('易支付'), value: 'epay' }, { label: t('易支付'), value: 'epay' },
{ label: t('Yipay'), value: 'yipay' }, { label: t('Yipay'), value: 'yipay' },
]} ]}
/> />
</Col> </Col>
<Col <Col
xs={24} xs={24} sm={24} md={8} lg={8} xl={8}
sm={24}
md={8}
lg={8}
xl={8}
style={{ style={{
display: currentProvider === 'epay' ? 'block' : 'none', display: currentProvider === 'alipay' ? 'flex' : 'none',
alignItems: 'center',
paddingTop: 30,
}} }}
> >
<Checkbox
checked={inputs.AlipayEnabled}
onChange={(e) => { const val = e.target.checked; setInputs((prev) => ({ ...prev, AlipayEnabled: val })); }}
>
{t('启用支付宝支付')}
</Checkbox>
</Col>
<Col
xs={24} sm={24} md={8} lg={8} xl={8}
style={{
display: currentProvider === 'wechatpay' ? 'flex' : 'none',
alignItems: 'center',
paddingTop: 30,
}}
>
<Checkbox
checked={inputs.WechatPayEnabled}
onChange={(e) => { const val = e.target.checked; setInputs((prev) => ({ ...prev, WechatPayEnabled: val })); }}
>
{t('启用微信支付')}
</Checkbox>
</Col>
</Row><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}
style={{
display: currentProvider === 'epay' ? 'flex' : 'none',
alignItems: 'center',
paddingTop: 30,
}}
>
<Checkbox
checked={inputs.EpayEnabled}
onChange={(e) => { const val = e.target.checked; setInputs((prev) => ({ ...prev, EpayEnabled: val })); }}
>
{t('启用易支付')}
</Checkbox>
</Col>
<Col
xs={24} sm={24} md={8} lg={8} xl={8}
style={{
display: currentProvider === 'yipay' ? 'flex' : 'none',
alignItems: 'center',
paddingTop: 30,
}}
>
<Checkbox
checked={inputs.YipayEnabled}
onChange={(e) => { const val = e.target.checked; setInputs((prev) => ({ ...prev, YipayEnabled: val })); }}
>
{t('启用 Yipay')}
</Checkbox>
</Col>
</Row><Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 8, display: currentProvider === 'alipay' ? 'flex' : 'none' }}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="AlipayAppId" label={t('支付宝应用ID')} placeholder={t('请填写支付宝应用ID')} />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="AlipayPrivateKey" label={t('支付宝私钥')} placeholder={t('请填写支付宝私钥')} type="password" />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="AlipayPublicKey" label={t('支付宝公钥')} placeholder={t('请填写支付宝公钥')} />
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 8, display: currentProvider === 'wechatpay' ? 'flex' : 'none' }}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="WechatPayAppId" label={t('微信支付应用ID')} placeholder={t('请填写微信支付应用ID')} />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="WechatPayMchId" label={t('微信支付商户号')} placeholder={t('请填写微信支付商户号')} />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="WechatPayApiV3Key" label={t('微信支付 API V3 密钥')} placeholder={t('请填写 API V3 密钥')} type="password" />
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 8, display: currentProvider === 'epay' ? 'flex' : 'none' }}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="PayAddress" label={t('支付地址')} placeholder={t('例如https://yourdomain.com')} />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="EpayId" label={t('易支付商户号')} placeholder={t('例如0001')} />
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input field="EpayKey" label={t('易支付商户密钥')} placeholder={t('敏感信息不会发送到前端显示')} type="password" />
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 8, display: currentProvider === 'yipay' ? 'flex' : 'none' }}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input <Form.Input
field='PayAddress' field="YipayRequestURL"
label={t('支付地址')} label={t('支付地址')}
placeholder={t('例如https://yourdomain.com')} placeholder={t('例如https://pay.xxx.com与易支付网关根地址相同')}
extraText={t('对应配置项 YipayRequestURL保存时会同步写入 PayAddress供服务端与易支付字段兼容。')}
/> />
</Col> </Col>
<Col <Col xs={24} sm={24} md={8} lg={8} xl={8}>
xs={24} <Form.Input field="YipayMchNo" label={t('Yipay 商户号(可选覆盖)')} placeholder={t('例如M1691xxxx')} />
sm={24}
md={8}
lg={8}
xl={8}
style={{
display: currentProvider === 'yipay' ? 'block' : 'none',
}}
>
<Form.Input
field='YipayRequestURL'
label={t('支付地址')}
placeholder={t(
'例如https://pay.xxx.com与易支付网关根地址相同',
)}
extraText={t(
'对应配置项 YipayRequestURL保存时会同步写入 PayAddress供服务端与易支付字段兼容。',
)}
/>
</Col> </Col>
<Col <Col xs={24} sm={24} md={8} lg={8} xl={8}>
xs={24} <Form.Input field="YipayAppId" label={t('Yipay 应用ID')} placeholder={t('例如62b2f8f6xxxxxx')} />
sm={24}
md={8}
lg={8}
xl={8}
style={{
display: currentProvider === 'epay' ? 'block' : 'none',
}}
>
<Form.Input
field='EpayId'
label={t('易支付商户号')}
placeholder={t('例如0001')}
/>
</Col>
<Col
xs={24}
sm={24}
md={8}
lg={8}
xl={8}
style={{
display: currentProvider === 'epay' ? 'block' : 'none',
}}
>
<Form.Input
field='EpayKey'
label={t('易支付商户密钥')}
placeholder={t('敏感信息不会发送到前端显示')}
type='password'
/>
</Col> </Col>
</Row> </Row>
<Row <Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ style={{ marginTop: 16, display: currentProvider === 'yipay' ? 'flex' : 'none' }}
marginTop: 16,
display: currentProvider === 'yipay' ? 'flex' : 'none',
}}
> >
<Col xs={24} sm={24} md={8} lg={8} xl={8}> <Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input <Form.Input field="YipayAppSecret" label={t('Yipay AppSecret')} placeholder={t('用于 MD5 签名的密钥(后端脱敏回显;修改后保存才会更新)')} autoComplete="off" />
field='YipayMchNo'
label={t('Yipay 商户号(可选覆盖)')}
placeholder={t('例如M1691xxxx')}
/>
</Col> </Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}> <Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input <Form.Input field="YipayNotifyUrl" label={t('Yipay 异步通知地址')} placeholder={t('可留空,留空则使用下方"回调地址"或服务器地址拼接默认路径')} />
field='YipayAppId'
label={t('Yipay 应用ID')}
placeholder={t('例如62b2f8f6xxxxxx')}
/>
</Col> </Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}> <Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input <Form.Input field="YipayReturnUrl" label={t('Yipay 同步通知地址')} placeholder={t('例如https://yourdomain.com/console/log')} />
field='YipayAppSecret'
label={t('Yipay AppSecret')}
placeholder={t(
'用于 MD5 签名的密钥(后端脱敏回显;修改后保存才会更新)',
)}
autoComplete='off'
/>
</Col> </Col>
</Row> </Row>
<Row <Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{ marginTop: 16 }} style={{ marginTop: 8, display: currentProvider === 'yipay' ? 'flex' : 'none' }}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input
field='CustomCallbackAddress'
label={t('回调地址')}
placeholder={t('例如https://yourdomain.com')}
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.InputNumber
field='Price'
precision={2}
label={t('充值价格x元/美金)')}
placeholder={t('例如7就是7元/美金')}
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.InputNumber
field='MinTopUp'
label={t('最低充值美元数量')}
placeholder={t('例如2就是最低充值2$')}
/>
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{
marginTop: 16,
display: currentProvider === 'yipay' ? 'flex' : 'none',
}}
>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input
field='YipayNotifyUrl'
label={t('Yipay 异步通知地址')}
placeholder={t(
'可留空,留空则使用下方「回调地址」或服务器地址拼接默认路径',
)}
extraText={(() => {
const hint = getDefaultYipayNotifyHint(
props.options.ServerAddress,
props.options.CustomCallbackAddress,
);
return hint
? `默认:${hint}(留空时与后端异步通知规则一致)`
: '请先配置「服务器地址」或「回调地址」以预览默认异步通知 URL';
})()}
/>
</Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input
field='YipayReturnUrl'
label={t('Yipay 同步通知地址')}
placeholder={t('例如https://yourdomain.com/console/log')}
extraText={(() => {
const hint = getDefaultYipayReturnHint(
props.options.ServerAddress,
);
return hint
? `默认:${hint}(留空时使用)`
: '请先配置「服务器地址」以预览默认同步跳转 URL';
})()}
/>
</Col>
</Row>
<Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
style={{
marginTop: 8,
display: currentProvider === 'yipay' ? 'flex' : 'none',
}}
> >
<Col span={24}> <Col span={24}>
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
@ -659,77 +679,39 @@ export default function SettingsPaymentGateway(props) {
<TextArea <TextArea
value={inputs.YipayChannelExtra} value={inputs.YipayChannelExtra}
onChange={handleYipayChannelExtraChange} onChange={handleYipayChannelExtraChange}
placeholder={'{"payDataType":"payUrl"}'} placeholder='{"payDataType":"payUrl"}'
rows={1} rows={1}
autoComplete='off' autoComplete="off"
style={{ style={{ width: "100%", fontFamily: "monospace", fontSize: 12 }}
width: '100%',
fontFamily: 'monospace',
fontSize: 12,
}}
/> />
<Text <Text type="tertiary" size="small" style={{ marginTop: 8, display: "block" }}>
type='tertiary' {t('可选。Jeepay channelExtraJSON。留空时一般 _PC 会附带 payDataType=payUrlPP_PC 会附带 payDataType 与 cancelUrl与 returnUrl 同源,见 yiPay PPPcOrderRQ。可在此覆盖 cancelUrl 等。')}
size='small'
style={{ marginTop: 8, display: 'block' }}
>
{t(
'可选。Jeepay channelExtraJSON。留空时一般 _PC 会附带 payDataType=payUrlPP_PC 会附带 payDataType 与 cancelUrl与 returnUrl 同源,见 yiPay PPPcOrderRQ。可在此覆盖 cancelUrl 等。',
)}
</Text> </Text>
</Col> </Col>
</Row> </Row>
<Form.TextArea <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} style={{ marginTop: 8 }}>
field='TopupGroupRatio' <Col xs={24} sm={24} md={8} lg={8} xl={8}>
label={t('充值分组倍率')} <Form.Input field="CustomCallbackAddress" label={t('回调地址')} placeholder={t('例如https://yourdomain.com')} />
placeholder={t('为一个 JSON 文本,键为组名称,值为倍率')} </Col>
autosize <Col xs={24} sm={24} md={8} lg={8} xl={8}>
/> <Form.InputNumber field="Price" precision={2} label={t('充值价格x元/美金)')} placeholder={t('例如7就是7元/美金')} />
<Form.TextArea </Col>
field='PayMethods' <Col xs={24} sm={24} md={8} lg={8} xl={8}>
label={t('充值方式设置')} <Form.InputNumber field="MinTopUp" label={t('最低充值美元数量')} placeholder={t('例如2就是最低充值2$')} />
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> </Col>
</Row> </Row>
<Form.TextArea field="TopupGroupRatio" label={t('充值分组倍率')} placeholder={t('为一个 JSON 文本,键为组名称,值为倍率')} autosize />
<Row <Form.TextArea field="PayMethods" label={t('充值方式设置')} placeholder={t('为一个 JSON 文本')} autosize />
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 }} style={{ marginTop: 16 }}>
style={{ marginTop: 16 }}
>
<Col span={24}> <Col span={24}>
<Form.TextArea <Form.TextArea field="AmountOptions" label={t('自定义充值数量选项')} placeholder={t('为一个 JSON 数组,例如:[10, 20, 50, 100, 200, 500]')} autosize extraText={t('设置用户可选择的充值数量选项,例如:[10, 20, 50, 100, 200, 500]')} />
field='AmountDiscount' </Col>
label={t('充值金额折扣配置')} </Row>
placeholder={t( <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} style={{ marginTop: 16 }}>
'为一个 JSON 对象,例如:{"100": 0.95, "200": 0.9, "500": 0.85}', <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}')} />
autosize
extraText={t(
'设置不同充值金额对应的折扣,键为充值金额,值为折扣率,例如:{"100": 0.95, "200": 0.9, "500": 0.85}',
)}
/>
</Col> </Col>
</Row> </Row>
<Button onClick={submitPayAddress}>{t('更新支付设置')}</Button> <Button onClick={submitPayAddress}>{t('更新支付设置')}</Button>
</Form.Section> </Form.Section>
</Form> </Form>