mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-22 16:27:01 +00:00
fix: backward compatibility for legacy purchase subscription system
- Add PurchaseRouter.vue to dynamically render PaymentView or PurchaseSubscriptionView based on payment_enabled vs purchase_subscription_enabled settings - Fix route guard to allow access when either payment system is enabled - Split sidebar menu: /purchase shows for both systems, /orders only for built-in payment - Remove auto-migration that forcibly disabled legacy purchase system on first startup (MigrateLegacyPurchaseURL) - Add payment_enabled to PublicSettings API response so frontend can check both systems from a single endpoint - Restore legacy purchase link config UI controls in admin Payment tab - Split payment settings save to use dedicated PUT /admin/payment/config API instead of mixing into general settings endpoint - Add admin payment config API functions (getConfig/updateConfig)
This commit is contained in:
@@ -128,6 +128,7 @@ type PublicSettings struct {
|
||||
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
|
||||
LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"`
|
||||
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
||||
PaymentEnabled bool `json:"payment_enabled"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
|
||||
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
|
||||
LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled,
|
||||
BackendModeEnabled: settings.BackendModeEnabled,
|
||||
PaymentEnabled: settings.PaymentEnabled,
|
||||
Version: h.version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -149,11 +149,12 @@ func NewPaymentService(entClient *dbent.Client, registry *payment.Registry, load
|
||||
return &PaymentService{entClient: entClient, registry: registry, loadBalancer: loadBalancer, redeemService: redeemService, subscriptionSvc: subscriptionSvc, configService: configService, userRepo: userRepo, groupRepo: groupRepo}
|
||||
}
|
||||
|
||||
// Start performs one-time startup tasks such as legacy settings migration.
|
||||
func (s *PaymentService) Start(ctx context.Context) {
|
||||
if err := s.configService.MigrateLegacyPurchaseURL(ctx); err != nil {
|
||||
slog.Error("[payment] legacy purchase URL migration failed", "error", err)
|
||||
}
|
||||
// Start performs one-time startup tasks.
|
||||
// NOTE: MigrateLegacyPurchaseURL is intentionally NOT called here to avoid
|
||||
// automatically disabling the legacy purchase_subscription system on upgrade.
|
||||
// Admins should trigger migration manually when ready to switch to the new
|
||||
// payment system.
|
||||
func (s *PaymentService) Start(_ context.Context) {
|
||||
}
|
||||
|
||||
func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*CreateOrderResponse, error) {
|
||||
|
||||
@@ -163,6 +163,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
SettingKeyCustomEndpoints,
|
||||
SettingKeyLinuxDoConnectEnabled,
|
||||
SettingKeyBackendModeEnabled,
|
||||
SettingPaymentEnabled,
|
||||
}
|
||||
|
||||
settings, err := s.settingRepo.GetMultiple(ctx, keys)
|
||||
@@ -208,6 +209,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
|
||||
CustomEndpoints: settings[SettingKeyCustomEndpoints],
|
||||
LinuxDoOAuthEnabled: linuxDoEnabled,
|
||||
BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true",
|
||||
PaymentEnabled: settings[SettingPaymentEnabled] == "true",
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -255,6 +257,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
|
||||
CustomEndpoints json.RawMessage `json:"custom_endpoints"`
|
||||
LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"`
|
||||
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
||||
PaymentEnabled bool `json:"payment_enabled"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}{
|
||||
RegistrationEnabled: settings.RegistrationEnabled,
|
||||
@@ -280,6 +283,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
|
||||
CustomEndpoints: safeRawJSONArray(settings.CustomEndpoints),
|
||||
LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled,
|
||||
BackendModeEnabled: settings.BackendModeEnabled,
|
||||
PaymentEnabled: settings.PaymentEnabled,
|
||||
Version: s.version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ type PublicSettings struct {
|
||||
|
||||
LinuxDoOAuthEnabled bool
|
||||
BackendModeEnabled bool
|
||||
PaymentEnabled bool
|
||||
Version string
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,49 @@ import type {
|
||||
} from '@/types/payment'
|
||||
import type { BasePaginationResponse } from '@/types'
|
||||
|
||||
/** Admin-facing payment config returned by GET /admin/payment/config */
|
||||
export interface AdminPaymentConfig {
|
||||
enabled: boolean
|
||||
min_amount: number
|
||||
max_amount: number
|
||||
daily_limit: number
|
||||
order_timeout_minutes: number
|
||||
max_pending_orders: number
|
||||
enabled_payment_types: string[]
|
||||
balance_disabled: boolean
|
||||
load_balance_strategy: string
|
||||
product_name_prefix: string
|
||||
product_name_suffix: string
|
||||
}
|
||||
|
||||
/** Fields accepted by PUT /admin/payment/config (all optional via pointer semantics) */
|
||||
export interface UpdatePaymentConfigRequest {
|
||||
enabled?: boolean
|
||||
min_amount?: number
|
||||
max_amount?: number
|
||||
daily_limit?: number
|
||||
order_timeout_minutes?: number
|
||||
max_pending_orders?: number
|
||||
enabled_payment_types?: string[]
|
||||
balance_disabled?: boolean
|
||||
load_balance_strategy?: string
|
||||
product_name_prefix?: string
|
||||
product_name_suffix?: string
|
||||
}
|
||||
|
||||
export const adminPaymentAPI = {
|
||||
// ==================== Config ====================
|
||||
|
||||
/** Get payment configuration (admin view) */
|
||||
getConfig() {
|
||||
return apiClient.get<AdminPaymentConfig>('/admin/payment/config')
|
||||
},
|
||||
|
||||
/** Update payment configuration */
|
||||
updateConfig(data: UpdatePaymentConfigRequest) {
|
||||
return apiClient.put('/admin/payment/config', data)
|
||||
},
|
||||
|
||||
// ==================== Dashboard ====================
|
||||
|
||||
/** Get payment dashboard statistics */
|
||||
|
||||
@@ -40,7 +40,6 @@ export interface SystemSettings {
|
||||
hide_ccs_import_button: boolean
|
||||
purchase_subscription_enabled: boolean
|
||||
purchase_subscription_url: string
|
||||
payment_enabled: boolean
|
||||
sora_client_enabled: boolean
|
||||
backend_mode_enabled: boolean
|
||||
custom_menu_items: CustomMenuItem[]
|
||||
|
||||
@@ -560,12 +560,16 @@ const userNavItems = computed((): NavItem[] => {
|
||||
icon: RechargeSubscriptionIcon,
|
||||
hideInSimpleMode: true
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
{
|
||||
path: '/orders',
|
||||
label: t('nav.myOrders'),
|
||||
icon: OrderListIcon,
|
||||
hideInSimpleMode: true
|
||||
}
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
@@ -597,12 +601,16 @@ const personalNavItems = computed((): NavItem[] => {
|
||||
icon: RechargeSubscriptionIcon,
|
||||
hideInSimpleMode: true
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
{
|
||||
path: '/orders',
|
||||
label: t('nav.myOrders'),
|
||||
icon: OrderListIcon,
|
||||
hideInSimpleMode: true
|
||||
}
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
|
||||
@@ -4342,6 +4342,14 @@ export default {
|
||||
helpImageUrl: 'Help Image URL',
|
||||
manageProviders: 'Manage Providers',
|
||||
balancePaymentDisabled: 'Balance payment is disabled',
|
||||
customPurchaseLink: {
|
||||
title: 'Custom Purchase Link (Legacy)',
|
||||
description: 'Redirect users to an external purchase page instead of the built-in payment system',
|
||||
enabled: 'Enable Custom Purchase Link',
|
||||
enabledHint: 'When enabled, users will be redirected to the URL below for purchases',
|
||||
url: 'Purchase Page URL',
|
||||
urlHint: 'The external URL where users can purchase subscriptions or top up',
|
||||
},
|
||||
},
|
||||
smtp: {
|
||||
title: 'SMTP Settings',
|
||||
|
||||
@@ -4515,6 +4515,14 @@ export default {
|
||||
helpImageUrl: '帮助图片链接',
|
||||
manageProviders: '管理服务商',
|
||||
balancePaymentDisabled: '余额支付已禁用',
|
||||
customPurchaseLink: {
|
||||
title: '自定义充值链接(旧版)',
|
||||
description: '将用户重定向到外部充值页面,而不是使用内置支付系统',
|
||||
enabled: '启用自定义充值链接',
|
||||
enabledHint: '启用后,用户将被重定向到下方的 URL 进行充值',
|
||||
url: '充值页面 URL',
|
||||
urlHint: '用户购买订阅或充值的外部链接地址',
|
||||
},
|
||||
},
|
||||
smtp: {
|
||||
title: 'SMTP 设置',
|
||||
|
||||
@@ -192,14 +192,14 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/purchase',
|
||||
name: 'PurchaseSubscription',
|
||||
component: () => import('@/views/user/PaymentView.vue'),
|
||||
component: () => import('@/views/user/PurchaseRouter.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
requiresAdmin: false,
|
||||
title: 'Purchase Subscription',
|
||||
titleKey: 'purchase.title',
|
||||
descriptionKey: 'purchase.description',
|
||||
requiresPayment: true
|
||||
requiresEitherPayment: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -567,7 +567,7 @@ router.beforeEach((to, _from, next) => {
|
||||
}
|
||||
|
||||
|
||||
// Check payment requirement
|
||||
// Check payment requirement (internal payment system only)
|
||||
if (to.meta.requiresPayment) {
|
||||
const paymentEnabled = appStore.cachedPublicSettings?.payment_enabled
|
||||
if (!paymentEnabled) {
|
||||
@@ -576,6 +576,16 @@ router.beforeEach((to, _from, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Check either payment requirement (internal payment OR external purchase subscription)
|
||||
if (to.meta.requiresEitherPayment) {
|
||||
const paymentEnabled = appStore.cachedPublicSettings?.payment_enabled
|
||||
const purchaseSubscriptionEnabled = appStore.cachedPublicSettings?.purchase_subscription_enabled
|
||||
if (!paymentEnabled && !purchaseSubscriptionEnabled) {
|
||||
next(authStore.isAdmin ? '/admin/dashboard' : '/dashboard')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 简易模式下限制访问某些页面
|
||||
if (authStore.isSimpleMode) {
|
||||
const restrictedPaths = [
|
||||
|
||||
Vendored
+22
@@ -42,5 +42,27 @@ declare module 'vue-router' {
|
||||
* @default false
|
||||
*/
|
||||
hideInMenu?: boolean
|
||||
|
||||
/**
|
||||
* Whether this route requires internal payment system to be enabled
|
||||
* @default false
|
||||
*/
|
||||
requiresPayment?: boolean
|
||||
|
||||
/**
|
||||
* Whether this route requires either internal payment or external purchase subscription
|
||||
* @default false
|
||||
*/
|
||||
requiresEitherPayment?: boolean
|
||||
|
||||
/**
|
||||
* i18n key for the page title
|
||||
*/
|
||||
titleKey?: string
|
||||
|
||||
/**
|
||||
* i18n key for the page description
|
||||
*/
|
||||
descriptionKey?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const settings = await adminAPI.settings.getSettings()
|
||||
const [settings, paymentConfigResp] = await Promise.all([
|
||||
adminAPI.settings.getSettings(),
|
||||
adminAPI.payment.getConfig()
|
||||
])
|
||||
opsMonitoringEnabled.value = settings.ops_monitoring_enabled ?? true
|
||||
writeCachedBool('ops_monitoring_enabled_cached', opsMonitoringEnabled.value)
|
||||
|
||||
@@ -69,7 +72,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', () => {
|
||||
|
||||
customMenuItems.value = Array.isArray(settings.custom_menu_items) ? settings.custom_menu_items : []
|
||||
|
||||
paymentEnabled.value = settings.payment_enabled ?? false
|
||||
paymentEnabled.value = paymentConfigResp.data?.enabled ?? false
|
||||
writeCachedBool('payment_enabled_cached', paymentEnabled.value)
|
||||
|
||||
loaded.value = true
|
||||
|
||||
@@ -1637,7 +1637,80 @@
|
||||
</div><!-- /Tab: General -->
|
||||
|
||||
<!-- Tab: Email -->
|
||||
<!-- Tab: Payment --> <div v-show="activeTab === 'payment'" class="space-y-6"> <div class="card"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t('admin.settings.payment.title') }}</h2> <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.description') }}</p> </div> <div class="space-y-6 p-6"> <div class="flex items-center justify-between"> <div> <label class="font-medium text-gray-900 dark:text-white">{{ t('admin.settings.payment.enabled') }}</label> <p class="text-sm text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.enabledHint') }}</p> </div> <Toggle v-model="form.payment_enabled" /> </div> <template v-if="form.payment_enabled"> <div class="grid grid-cols-2 gap-4"> <div><label class="input-label">{{ t('admin.settings.payment.minAmount') }}</label><input v-model.number="form.payment_min_amount" type="number" step="0.01" min="0" class="input" /></div> <div><label class="input-label">{{ t('admin.settings.payment.maxAmount') }}</label><input v-model.number="form.payment_max_amount" type="number" step="0.01" min="0" class="input" /></div> </div> <div class="grid grid-cols-2 gap-4"> <div><label class="input-label">{{ t('admin.settings.payment.dailyLimit') }}</label><input v-model.number="form.payment_daily_limit" type="number" step="0.01" min="0" class="input" /></div> <div><label class="input-label">{{ t('admin.settings.payment.maxPendingOrders') }}</label><input v-model.number="form.payment_max_pending_orders" type="number" min="1" class="input" /></div> </div> <div class="grid grid-cols-2 gap-4"> <div><label class="input-label">{{ t('admin.settings.payment.orderTimeout') }}</label><input v-model.number="form.payment_order_timeout_minutes" type="number" min="1" class="input" /></div> <div class="flex items-center gap-2 pt-6"><input id="balance-disabled" v-model="form.payment_balance_disabled" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500" /><label for="balance-disabled" class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.settings.payment.balancePaymentDisabled') }}</label></div> </div> <div><label class="input-label">{{ t('admin.settings.payment.enabledPaymentTypes') }}</label><input v-model="form.payment_enabled_types" type="text" class="input" placeholder="alipay,wxpay,stripe" /><p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.enabledPaymentTypesHint') }}</p></div> <div class="grid grid-cols-2 gap-4"> <div><label class="input-label">{{ t('admin.settings.payment.helpImageUrl') }}</label><input v-model="form.payment_help_image_url" type="url" class="input" /></div> <div><label class="input-label">{{ t('admin.settings.payment.helpText') }}</label><input v-model="form.payment_help_text" type="text" class="input" /></div> </div> <div> <a href="/admin/orders/providers" class="text-sm text-blue-600 hover:underline dark:text-blue-400">{{ t('admin.settings.payment.manageProviders') }} →</a> </div> </template> </div> </div> </div>
|
||||
<!-- Tab: Payment -->
|
||||
<div v-show="activeTab === 'payment'" class="space-y-6">
|
||||
|
||||
<!-- Payment System Settings -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t('admin.settings.payment.title') }}</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.description') }}</p>
|
||||
</div>
|
||||
<div class="space-y-6 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{ t('admin.settings.payment.enabled') }}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.enabledHint') }}</p>
|
||||
</div>
|
||||
<Toggle v-model="form.payment_enabled" />
|
||||
</div>
|
||||
<template v-if="form.payment_enabled">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.minAmount') }}</label><input v-model.number="form.payment_min_amount" type="number" step="0.01" min="0" class="input" /></div>
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.maxAmount') }}</label><input v-model.number="form.payment_max_amount" type="number" step="0.01" min="0" class="input" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.dailyLimit') }}</label><input v-model.number="form.payment_daily_limit" type="number" step="0.01" min="0" class="input" /></div>
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.maxPendingOrders') }}</label><input v-model.number="form.payment_max_pending_orders" type="number" min="1" class="input" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.orderTimeout') }}</label><input v-model.number="form.payment_order_timeout_minutes" type="number" min="1" class="input" /></div>
|
||||
<div class="flex items-center gap-2 pt-6"><input id="balance-disabled" v-model="form.payment_balance_disabled" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500" /><label for="balance-disabled" class="text-sm text-gray-700 dark:text-gray-300">{{ t('admin.settings.payment.balancePaymentDisabled') }}</label></div>
|
||||
</div>
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.enabledPaymentTypes') }}</label><input v-model="form.payment_enabled_types" type="text" class="input" placeholder="alipay,wxpay,stripe" /><p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.enabledPaymentTypesHint') }}</p></div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.helpImageUrl') }}</label><input v-model="form.payment_help_image_url" type="url" class="input" /></div>
|
||||
<div><label class="input-label">{{ t('admin.settings.payment.helpText') }}</label><input v-model="form.payment_help_text" type="text" class="input" /></div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/admin/orders/providers" class="text-sm text-blue-600 hover:underline dark:text-blue-400">{{ t('admin.settings.payment.manageProviders') }} →</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Purchase Link (Legacy) -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t('admin.settings.payment.customPurchaseLink.title') }}</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.customPurchaseLink.description') }}</p>
|
||||
</div>
|
||||
<div class="space-y-4 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{ t('admin.settings.payment.customPurchaseLink.enabled') }}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('admin.settings.payment.customPurchaseLink.enabledHint') }}</p>
|
||||
</div>
|
||||
<Toggle v-model="form.purchase_subscription_enabled" />
|
||||
</div>
|
||||
<div v-if="form.purchase_subscription_enabled">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.payment.customPurchaseLink.url') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.purchase_subscription_url"
|
||||
type="url"
|
||||
class="input"
|
||||
placeholder="https://..."
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.payment.customPurchaseLink.urlHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-show="activeTab === 'email'" class="space-y-6">
|
||||
<!-- Email disabled hint - show when email_verify_enabled is off -->
|
||||
<div v-if="!form.email_verify_enabled" class="card">
|
||||
@@ -2013,6 +2086,17 @@ type SettingsForm = SystemSettings & {
|
||||
smtp_password: string
|
||||
turnstile_secret_key: string
|
||||
linuxdo_connect_client_secret: string
|
||||
// Payment config fields (loaded from/saved to separate payment config API)
|
||||
payment_enabled: boolean
|
||||
payment_min_amount: number
|
||||
payment_max_amount: number
|
||||
payment_daily_limit: number
|
||||
payment_max_pending_orders: number
|
||||
payment_order_timeout_minutes: number
|
||||
payment_balance_disabled: boolean
|
||||
payment_enabled_types: string
|
||||
payment_help_image_url: string
|
||||
payment_help_text: string
|
||||
}
|
||||
|
||||
const form = reactive<SettingsForm>({
|
||||
@@ -2227,7 +2311,10 @@ async function loadSettings() {
|
||||
loading.value = true
|
||||
loadFailed.value = false
|
||||
try {
|
||||
const settings = await adminAPI.settings.getSettings()
|
||||
const [settings, paymentConfigResp] = await Promise.all([
|
||||
adminAPI.settings.getSettings(),
|
||||
adminAPI.payment.getConfig()
|
||||
])
|
||||
Object.assign(form, settings)
|
||||
form.backend_mode_enabled = settings.backend_mode_enabled
|
||||
form.default_subscriptions = Array.isArray(settings.default_subscriptions)
|
||||
@@ -2246,6 +2333,17 @@ async function loadSettings() {
|
||||
smtpPasswordManuallyEdited.value = false
|
||||
form.turnstile_secret_key = ''
|
||||
form.linuxdo_connect_client_secret = ''
|
||||
|
||||
// Populate payment config fields from the dedicated payment config API
|
||||
const paymentConfig = paymentConfigResp.data
|
||||
form.payment_enabled = paymentConfig.enabled
|
||||
form.payment_min_amount = paymentConfig.min_amount
|
||||
form.payment_max_amount = paymentConfig.max_amount
|
||||
form.payment_daily_limit = paymentConfig.daily_limit
|
||||
form.payment_max_pending_orders = paymentConfig.max_pending_orders
|
||||
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(',')
|
||||
} catch (error: any) {
|
||||
loadFailed.value = true
|
||||
appStore.showError(
|
||||
@@ -2348,17 +2446,6 @@ async function saveSettings() {
|
||||
hide_ccs_import_button: form.hide_ccs_import_button,
|
||||
purchase_subscription_enabled: form.purchase_subscription_enabled,
|
||||
purchase_subscription_url: form.purchase_subscription_url,
|
||||
payment_enabled: form.payment_enabled,
|
||||
payment_min_amount: form.payment_min_amount,
|
||||
payment_max_amount: form.payment_max_amount,
|
||||
payment_daily_limit: form.payment_daily_limit,
|
||||
payment_max_pending_orders: form.payment_max_pending_orders,
|
||||
payment_order_timeout_minutes: form.payment_order_timeout_minutes,
|
||||
payment_balance_disabled: form.payment_balance_disabled,
|
||||
payment_enabled_types: form.payment_enabled_types,
|
||||
payment_help_image_url: form.payment_help_image_url,
|
||||
payment_help_text: form.payment_help_text,
|
||||
sora_client_enabled: form.sora_client_enabled,
|
||||
custom_menu_items: form.custom_menu_items,
|
||||
custom_endpoints: form.custom_endpoints,
|
||||
frontend_url: form.frontend_url,
|
||||
@@ -2389,7 +2476,26 @@ async function saveSettings() {
|
||||
enable_fingerprint_unification: form.enable_fingerprint_unification,
|
||||
enable_metadata_passthrough: form.enable_metadata_passthrough
|
||||
}
|
||||
const updated = await adminAPI.settings.updateSettings(payload)
|
||||
|
||||
// Save payment config via dedicated payment config API
|
||||
const enabledTypes = form.payment_enabled_types
|
||||
? form.payment_enabled_types.split(',').map((s: string) => s.trim()).filter(Boolean)
|
||||
: []
|
||||
const paymentPayload = {
|
||||
enabled: form.payment_enabled,
|
||||
min_amount: form.payment_min_amount,
|
||||
max_amount: form.payment_max_amount,
|
||||
daily_limit: form.payment_daily_limit,
|
||||
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
|
||||
}
|
||||
|
||||
const [updated] = await Promise.all([
|
||||
adminAPI.settings.updateSettings(payload),
|
||||
adminAPI.payment.updateConfig(paymentPayload)
|
||||
])
|
||||
Object.assign(form, updated)
|
||||
registrationEmailSuffixWhitelistTags.value = normalizeRegistrationEmailSuffixDomains(
|
||||
updated.registration_email_suffix_whitelist
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<PaymentView v-if="paymentEnabled" />
|
||||
<PurchaseSubscriptionView v-else-if="purchaseSubscriptionEnabled" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import PaymentView from './PaymentView.vue'
|
||||
import PurchaseSubscriptionView from './PurchaseSubscriptionView.vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const paymentEnabled = computed(() => appStore.cachedPublicSettings?.payment_enabled ?? false)
|
||||
const purchaseSubscriptionEnabled = computed(
|
||||
() => appStore.cachedPublicSettings?.purchase_subscription_enabled ?? false
|
||||
)
|
||||
</script>
|
||||
Reference in New Issue
Block a user