diff --git a/frontend/src/components/layout/AppSidebar.vue b/frontend/src/components/layout/AppSidebar.vue
index 37509a7eb0..ca84337830 100644
--- a/frontend/src/components/layout/AppSidebar.vue
+++ b/frontend/src/components/layout/AppSidebar.vue
@@ -683,7 +683,6 @@ const adminNavItems = computed((): NavItem[] => {
children: [
{ path: '/admin/orders/dashboard', label: t('nav.paymentDashboard'), icon: ChartIcon },
{ path: '/admin/orders', label: t('nav.orderManagement'), icon: OrderIcon },
- { path: '/admin/orders/config', label: t('nav.paymentConfig'), icon: CogIcon },
{ path: '/admin/orders/plans', label: t('nav.paymentPlans'), icon: CreditCardIcon },
],
},
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 417cfec403..55b5391a2e 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -4344,7 +4344,27 @@ export default {
helpText: 'Help Text',
helpImageUrl: 'Help Image URL',
manageProviders: 'Manage Providers',
- balancePaymentDisabled: 'Balance payment is disabled',
+ balancePaymentDisabled: 'Disable Balance Recharge',
+ productNamePrefix: 'Product Name Prefix',
+ productNameSuffix: 'Product Name Suffix',
+ preview: 'Preview',
+ loadBalanceStrategy: 'Load Balance Strategy',
+ strategyRoundRobin: 'Round Robin',
+ strategyLeastAmount: 'Least Daily Amount',
+ providerManagement: 'Provider Management',
+ providerManagementDesc: 'Manage payment provider instances',
+ createProvider: 'Add Provider',
+ editProvider: 'Edit Provider',
+ deleteProvider: 'Delete Provider',
+ deleteProviderConfirm: 'Are you sure you want to delete this provider?',
+ providerName: 'Provider Name',
+ providerKey: 'Provider Type',
+ selectProviderKey: 'Select Provider Type',
+ providerConfig: 'Credentials',
+ noProviders: 'No provider instances configured',
+ supportedTypes: 'Supported Payment Types',
+ supportedTypesHint: 'Comma-separated, e.g. alipay,wxpay',
+ refundEnabled: 'Allow Refund',
},
smtp: {
title: 'SMTP Settings',
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index cbf969bf27..47cd3918f4 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -4517,7 +4517,27 @@ export default {
helpText: '帮助文本',
helpImageUrl: '帮助图片链接',
manageProviders: '管理服务商',
- balancePaymentDisabled: '余额支付已禁用',
+ balancePaymentDisabled: '禁用余额充值',
+ productNamePrefix: '商品名前缀',
+ productNameSuffix: '商品名后缀',
+ preview: '预览',
+ loadBalanceStrategy: '负载均衡策略',
+ strategyRoundRobin: '轮询',
+ strategyLeastAmount: '最少金额',
+ providerManagement: '服务商管理',
+ providerManagementDesc: '管理支付服务商实例',
+ createProvider: '添加服务商',
+ editProvider: '编辑服务商',
+ deleteProvider: '删除服务商',
+ deleteProviderConfirm: '确定要删除此服务商吗?',
+ providerName: '服务商名称',
+ providerKey: '服务商类型',
+ selectProviderKey: '选择服务商类型',
+ providerConfig: '凭证配置',
+ noProviders: '暂无服务商实例',
+ supportedTypes: '支持的支付方式',
+ supportedTypesHint: '逗号分隔,如 alipay,wxpay',
+ refundEnabled: '允许退款',
},
smtp: {
title: 'SMTP 设置',
diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue
index 6cf8ed6de2..26e9f991ae 100644
--- a/frontend/src/views/admin/SettingsView.vue
+++ b/frontend/src/views/admin/SettingsView.vue
@@ -1640,13 +1640,14 @@
-
+
{{ t('admin.settings.payment.title') }}
{{ t('admin.settings.payment.description') }}
+
@@ -1655,31 +1656,146 @@
+
+
+
+
+
{{ (form.payment_product_name_prefix || 'Sub2API') + ' 100 ' + (form.payment_product_name_suffix || 'CNY') }}
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
{{ t('admin.settings.payment.providerManagement') }}
+
{{ t('admin.settings.payment.providerManagementDesc') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ provider.name }}
+
{{ providerKeyLabel(provider.provider_key) }}
+
+
+
{{ provider.enabled ? t('common.enabled') : t('common.disabled') }}
+
+
+
+ {{ t('admin.settings.payment.supportedTypes') }}:
+ {{ provider.supported_types || '-' }}
+
+
+ {{ t('admin.settings.payment.refundEnabled') }}:
+ {{ provider.refund_enabled ? 'Yes' : 'No' }}
+
+
+
+
+
+
+
+
+
+
+
{{ t('admin.settings.payment.noProviders') }}
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1946,9 +2062,12 @@ import type {
DefaultSubscriptionSetting
} from '@/api/admin/settings'
import type { AdminGroup } from '@/types'
+import type { ProviderInstance } from '@/types/payment'
import AppLayout from '@/components/layout/AppLayout.vue'
import Icon from '@/components/icons/Icon.vue'
import Select from '@/components/common/Select.vue'
+import BaseDialog from '@/components/common/BaseDialog.vue'
+import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import GroupBadge from '@/components/common/GroupBadge.vue'
import GroupOptionItem from '@/components/common/GroupOptionItem.vue'
import Toggle from '@/components/common/Toggle.vue'
@@ -2066,6 +2185,9 @@ type SettingsForm = SystemSettings & {
payment_enabled_types: string
payment_help_image_url: string
payment_help_text: string
+ payment_product_name_prefix: string
+ payment_product_name_suffix: string
+ payment_load_balance_strategy: string
}
const form = reactive
({
@@ -2089,7 +2211,7 @@ const form = reactive({
home_content: '',
backend_mode_enabled: false,
hide_ccs_import_button: false,
- payment_enabled: false, payment_min_amount: 1, payment_max_amount: 10000, payment_daily_limit: 50000, payment_max_pending_orders: 3, payment_order_timeout_minutes: 30, payment_balance_disabled: false, payment_enabled_types: '', payment_help_image_url: '', payment_help_text: '',
+ payment_enabled: false, payment_min_amount: 1, payment_max_amount: 10000, payment_daily_limit: 50000, payment_max_pending_orders: 3, payment_order_timeout_minutes: 30, payment_balance_disabled: false, payment_enabled_types: '', payment_help_image_url: '', payment_help_text: '', payment_product_name_prefix: '', payment_product_name_suffix: '', payment_load_balance_strategy: 'round-robin',
sora_client_enabled: false,
custom_menu_items: [] as Array<{id: string; label: string; icon_svg: string; url: string; visibility: 'user' | 'admin'; sort_order: number}>,
custom_endpoints: [] as Array<{name: string; endpoint: string; description: string}>,
@@ -2311,6 +2433,9 @@ async function loadSettings() {
form.payment_order_timeout_minutes = paymentConfig.order_timeout_minutes
form.payment_balance_disabled = paymentConfig.balance_disabled
form.payment_enabled_types = (paymentConfig.enabled_payment_types || []).join(',')
+ form.payment_product_name_prefix = paymentConfig.product_name_prefix || ''
+ form.payment_product_name_suffix = paymentConfig.product_name_suffix || ''
+ form.payment_load_balance_strategy = paymentConfig.load_balance_strategy || 'round-robin'
} catch (error: any) {
loadFailed.value = true
appStore.showError(
@@ -2454,7 +2579,10 @@ async function saveSettings() {
max_pending_orders: form.payment_max_pending_orders,
order_timeout_minutes: form.payment_order_timeout_minutes,
balance_disabled: form.payment_balance_disabled,
- enabled_payment_types: enabledTypes
+ enabled_payment_types: enabledTypes,
+ product_name_prefix: form.payment_product_name_prefix,
+ product_name_suffix: form.payment_product_name_suffix,
+ load_balance_strategy: form.payment_load_balance_strategy,
}
const [updated] = await Promise.all([
@@ -2756,6 +2884,76 @@ async function saveBetaPolicySettings() {
}
}
+// ==================== Provider Management ====================
+
+const providersLoading = ref(false)
+const providerSaving = ref(false)
+const providers = ref([])
+const showProviderDialog = ref(false)
+const showDeleteProviderDialog = ref(false)
+const editingProvider = ref(null)
+const deletingProviderId = ref(null)
+
+const providerForm = reactive({
+ name: '', provider_key: 'easypay', supported_types: '', enabled: true, refund_enabled: false,
+})
+const providerConfig = reactive({
+ pid: '', pkey: '', apiBase: '', notifyUrl: '', returnUrl: '',
+ cid: '', cidAlipay: '', cidWxpay: '',
+ appId: '', privateKey: '', publicKey: '',
+ mchId: '', apiV3Key: '', publicKeyId: '', certSerial: '',
+ secretKey: '', publishableKey: '', webhookSecret: '',
+})
+const providerKeyOptions = [
+ { value: 'easypay', label: 'EasyPay' },
+ { value: 'alipay', label: 'Alipay (Direct)' },
+ { value: 'wxpay', label: 'WeChat Pay (Direct)' },
+ { value: 'stripe', label: 'Stripe' },
+]
+const loadBalanceOptions = computed(() => [
+ { value: 'round-robin', label: t('admin.settings.payment.strategyRoundRobin') },
+ { value: 'least-amount', label: t('admin.settings.payment.strategyLeastAmount') },
+])
+function providerKeyLabel(key: string): string {
+ return providerKeyOptions.find(o => o.value === key)?.label || key
+}
+async function loadProviders() {
+ providersLoading.value = true
+ try { const res = await adminAPI.payment.getProviders(); providers.value = res.data || [] }
+ catch (err: unknown) { appStore.showError(err instanceof Error ? err.message : String(err)) }
+ finally { providersLoading.value = false }
+}
+function resetProviderForm() {
+ providerForm.name = ''; providerForm.provider_key = 'easypay'; providerForm.supported_types = ''
+ providerForm.enabled = true; providerForm.refund_enabled = false
+ Object.keys(providerConfig).forEach(k => { (providerConfig as any)[k] = '' })
+}
+function openCreateProvider() { editingProvider.value = null; resetProviderForm(); showProviderDialog.value = true }
+function openEditProvider(provider: ProviderInstance) {
+ editingProvider.value = provider
+ providerForm.name = provider.name; providerForm.provider_key = provider.provider_key
+ providerForm.supported_types = provider.supported_types; providerForm.enabled = provider.enabled
+ providerForm.refund_enabled = provider.refund_enabled
+ Object.keys(providerConfig).forEach(k => { (providerConfig as any)[k] = '' })
+ showProviderDialog.value = true
+}
+async function handleSaveProvider() {
+ providerSaving.value = true
+ try {
+ const data = { ...providerForm, config: { ...providerConfig } } as any
+ if (editingProvider.value) { await adminAPI.payment.updateProvider(editingProvider.value.id, data) }
+ else { await adminAPI.payment.createProvider(data) }
+ appStore.showSuccess(t('common.saved')); showProviderDialog.value = false; loadProviders()
+ } catch (err: unknown) { appStore.showError(err instanceof Error ? err.message : String(err)) }
+ finally { providerSaving.value = false }
+}
+function confirmDeleteProvider(provider: ProviderInstance) { deletingProviderId.value = provider.id; showDeleteProviderDialog.value = true }
+async function handleDeleteProvider() {
+ if (!deletingProviderId.value) return
+ try { await adminAPI.payment.deleteProvider(deletingProviderId.value); appStore.showSuccess(t('common.deleted')); showDeleteProviderDialog.value = false; loadProviders() }
+ catch (err: unknown) { appStore.showError(err instanceof Error ? err.message : String(err)) }
+}
+
onMounted(() => {
loadSettings()
loadSubscriptionGroups()
@@ -2764,6 +2962,7 @@ onMounted(() => {
loadStreamTimeoutSettings()
loadRectifierSettings()
loadBetaPolicySettings()
+ loadProviders()
})