diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go index 3936a4ccfa..fe8b88cd85 100644 --- a/backend/internal/service/payment_config_service.go +++ b/backend/internal/service/payment_config_service.go @@ -448,7 +448,10 @@ func pcParseInt(s string, defaultVal int) int { // MigrateLegacyPurchaseURL checks if the old purchase_subscription_url setting // exists and is enabled, and if so, migrates it to a custom_menu_item entry. -// This is a one-time migration that runs at startup. +// After migration, both purchase_subscription_enabled and purchase_subscription_url +// are cleared since the functionality is fully replaced by custom menu items. +// This is idempotent: it skips if a menu item with ID "migrated_purchase_subscription" +// already exists. func (s *PaymentConfigService) MigrateLegacyPurchaseURL(ctx context.Context) error { enabled, _ := s.settingRepo.GetValue(ctx, SettingKeyPurchaseSubscriptionEnabled) if enabled != "true" { @@ -475,9 +478,9 @@ func (s *PaymentConfigService) MigrateLegacyPurchaseURL(ctx context.Context) err _ = json.Unmarshal([]byte(raw), &items) } - // Check if already migrated (same URL exists) + // Check if already migrated (same ID exists) for _, item := range items { - if item.URL == url { + if item.ID == "migrated_purchase_subscription" { return nil } } @@ -489,7 +492,7 @@ func (s *PaymentConfigService) MigrateLegacyPurchaseURL(ctx context.Context) err IconSVG: "", URL: url, Visibility: "user", - SortOrder: 0, + SortOrder: 100, }) data, err := json.Marshal(items) @@ -497,10 +500,11 @@ func (s *PaymentConfigService) MigrateLegacyPurchaseURL(ctx context.Context) err return fmt.Errorf("marshal custom_menu_items: %w", err) } - // Save updated menu items and clear old settings + // Save updated menu items and clear legacy settings entirely if err := s.settingRepo.SetMultiple(ctx, map[string]string{ SettingKeyCustomMenuItems: string(data), SettingKeyPurchaseSubscriptionEnabled: "false", + SettingKeyPurchaseSubscriptionURL: "", }); err != nil { return fmt.Errorf("save migrated settings: %w", err) } diff --git a/backend/internal/service/payment_service.go b/backend/internal/service/payment_service.go index 6af376e468..55ef050c5b 100644 --- a/backend/internal/service/payment_service.go +++ b/backend/internal/service/payment_service.go @@ -150,11 +150,10 @@ func NewPaymentService(entClient *dbent.Client, registry *payment.Registry, load } // 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) Start(ctx context.Context) { + if err := s.configService.MigrateLegacyPurchaseURL(ctx); err != nil { + slog.Error("[payment] failed to migrate legacy purchase URL", "error", err) + } } func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*CreateOrderResponse, error) { diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index 98baad5151..dce9d1d90a 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -38,8 +38,6 @@ export interface SystemSettings { doc_url: string home_content: string hide_ccs_import_button: boolean - purchase_subscription_enabled: boolean - purchase_subscription_url: string sora_client_enabled: boolean backend_mode_enabled: boolean custom_menu_items: CustomMenuItem[] @@ -112,8 +110,6 @@ export interface UpdateSettingsRequest { doc_url?: string home_content?: string hide_ccs_import_button?: boolean - purchase_subscription_enabled?: boolean - purchase_subscription_url?: string backend_mode_enabled?: boolean custom_menu_items?: CustomMenuItem[] custom_endpoints?: CustomEndpoint[] diff --git a/frontend/src/components/layout/AppSidebar.vue b/frontend/src/components/layout/AppSidebar.vue index a1efd4b4cb..b909178abf 100644 --- a/frontend/src/components/layout/AppSidebar.vue +++ b/frontend/src/components/layout/AppSidebar.vue @@ -552,7 +552,7 @@ const userNavItems = computed((): NavItem[] => { ...(appStore.cachedPublicSettings?.sora_client_enabled ? [{ path: '/sora', label: t('nav.sora'), icon: SoraIcon }] : []), - ...(appStore.cachedPublicSettings?.payment_enabled || appStore.cachedPublicSettings?.purchase_subscription_enabled + ...(appStore.cachedPublicSettings?.payment_enabled ? [ { path: '/purchase', @@ -593,7 +593,7 @@ const personalNavItems = computed((): NavItem[] => { ...(appStore.cachedPublicSettings?.sora_client_enabled ? [{ path: '/sora', label: t('nav.sora'), icon: SoraIcon }] : []), - ...(appStore.cachedPublicSettings?.payment_enabled || appStore.cachedPublicSettings?.purchase_subscription_enabled + ...(appStore.cachedPublicSettings?.payment_enabled ? [ { path: '/purchase', diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index c843ef22ef..86e2def63d 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -4342,14 +4342,6 @@ 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', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 645613410e..9901c8d705 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -4515,14 +4515,6 @@ export default { helpImageUrl: '帮助图片链接', manageProviders: '管理服务商', balancePaymentDisabled: '余额支付已禁用', - customPurchaseLink: { - title: '自定义充值链接(旧版)', - description: '将用户重定向到外部充值页面,而不是使用内置支付系统', - enabled: '启用自定义充值链接', - enabledHint: '启用后,用户将被重定向到下方的 URL 进行充值', - url: '充值页面 URL', - urlHint: '用户购买订阅或充值的外部链接地址', - }, }, smtp: { title: 'SMTP 设置', diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 2f240e93be..fc4870baf3 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -192,14 +192,14 @@ const routes: RouteRecordRaw[] = [ { path: '/purchase', name: 'PurchaseSubscription', - component: () => import('@/views/user/PurchaseRouter.vue'), + component: () => import('@/views/user/PaymentView.vue'), meta: { requiresAuth: true, requiresAdmin: false, title: 'Purchase Subscription', titleKey: 'purchase.title', descriptionKey: 'purchase.description', - requiresEitherPayment: true + requiresPayment: true } }, { @@ -576,16 +576,6 @@ 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 = [ diff --git a/frontend/src/router/meta.d.ts b/frontend/src/router/meta.d.ts index 96df04311d..7b2777c29d 100644 --- a/frontend/src/router/meta.d.ts +++ b/frontend/src/router/meta.d.ts @@ -49,12 +49,6 @@ declare module 'vue-router' { */ requiresPayment?: boolean - /** - * Whether this route requires either internal payment or external purchase subscription - * @default false - */ - requiresEitherPayment?: boolean - /** * i18n key for the page title */ diff --git a/frontend/src/stores/app.ts b/frontend/src/stores/app.ts index d4fe7b9860..e106f630b2 100644 --- a/frontend/src/stores/app.ts +++ b/frontend/src/stores/app.ts @@ -327,9 +327,7 @@ export const useAppStore = defineStore('app', () => { doc_url: docUrl.value, home_content: '', hide_ccs_import_button: false, - purchase_subscription_enabled: false, payment_enabled: false, - purchase_subscription_url: '', custom_menu_items: [], custom_endpoints: [], linuxdo_oauth_enabled: false, diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 1fc9316d60..24808b2183 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -104,9 +104,7 @@ export interface PublicSettings { doc_url: string home_content: string hide_ccs_import_button: boolean - purchase_subscription_enabled: boolean payment_enabled: boolean - purchase_subscription_url: string custom_menu_items: CustomMenuItem[] custom_endpoints: CustomEndpoint[] linuxdo_oauth_enabled: boolean diff --git a/frontend/src/utils/embedded-url.ts b/frontend/src/utils/embedded-url.ts index e70d30b4dd..1c79230100 100644 --- a/frontend/src/utils/embedded-url.ts +++ b/frontend/src/utils/embedded-url.ts @@ -1,6 +1,6 @@ /** * Shared URL builder for iframe-embedded pages. - * Used by PurchaseSubscriptionView and CustomPageView to build consistent URLs + * Used by CustomPageView to build consistent URLs * with user_id, token, theme, lang, ui_mode, src_host, and src parameters. */ diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index a29f70b9b1..194aecd1a6 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -1679,37 +1679,6 @@ - -
{{ t('admin.settings.payment.customPurchaseLink.description') }}
-{{ t('admin.settings.payment.customPurchaseLink.enabledHint') }}
-- {{ t('admin.settings.payment.customPurchaseLink.urlHint') }} -
-- {{ t('purchase.notEnabledDesc') }} -
-- {{ t('purchase.notConfiguredDesc') }} -
-