From 57cb01cd49eae2cf626f9ce368918ca0be423f51 Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 7 Apr 2026 02:54:03 +0800 Subject: [PATCH] refactor(payment): use string[] for supported_types throughout frontend+backend API - Backend API returns/accepts supported_types as string[] (converts to/from DB comma string) - Frontend ProviderInstance.supported_types is string[], no more parseTypes - Remove all split/join conversions for supported_types - payment_enabled_types also uses string[] consistently --- backend/cmd/server/VERSION | 2 +- .../service/payment_config_service.go | 34 +++++++++++++++---- .../payment/PaymentProviderDialog.vue | 22 ++++++------ .../src/components/payment/ProviderCard.vue | 3 +- frontend/src/types/payment.ts | 2 +- frontend/src/views/admin/SettingsView.vue | 14 +++----- 6 files changed, 46 insertions(+), 31 deletions(-) diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index d08bc86cca..bfc83f06f7 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.108.57 +0.1.108.58 diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go index 94a3d99f33..6a6822b037 100644 --- a/backend/internal/service/payment_config_service.go +++ b/backend/internal/service/payment_config_service.go @@ -91,7 +91,7 @@ type CreateProviderInstanceRequest struct { ProviderKey string `json:"providerKey"` Name string `json:"name"` Config map[string]string `json:"config"` - SupportedTypes string `json:"supportedTypes"` + SupportedTypes []string `json:"supportedTypes"` Enabled bool `json:"enabled"` SortOrder int `json:"sortOrder"` Limits string `json:"limits"` @@ -101,7 +101,7 @@ type CreateProviderInstanceRequest struct { type UpdateProviderInstanceRequest struct { Name *string `json:"name"` Config map[string]string `json:"config"` - SupportedTypes *string `json:"supportedTypes"` + SupportedTypes []string `json:"supportedTypes"` Enabled *bool `json:"enabled"` SortOrder *int `json:"sortOrder"` Limits *string `json:"limits"` @@ -257,6 +257,25 @@ func derefStr(v *string) string { return *v } +func splitTypes(s string) []string { + if s == "" { + return nil + } + parts := strings.Split(s, ",") + result := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p != "" { + result = append(result, p) + } + } + return result +} + +func joinTypes(types []string) string { + return strings.Join(types, ",") +} + // --- Provider Instance CRUD --- func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*dbent.PaymentProviderInstance, error) { @@ -270,7 +289,7 @@ type ProviderInstanceResponse struct { ProviderKey string `json:"provider_key"` Name string `json:"name"` Config map[string]string `json:"config"` - SupportedTypes string `json:"supported_types"` + SupportedTypes []string `json:"supported_types"` Limits string `json:"limits"` Enabled bool `json:"enabled"` RefundEnabled bool `json:"refund_enabled"` @@ -289,7 +308,7 @@ func (s *PaymentConfigService) ListProviderInstancesWithConfig(ctx context.Conte for _, inst := range instances { resp := ProviderInstanceResponse{ ID: int64(inst.ID), ProviderKey: inst.ProviderKey, Name: inst.Name, - SupportedTypes: inst.SupportedTypes, Limits: inst.Limits, + SupportedTypes: splitTypes(inst.SupportedTypes), Limits: inst.Limits, Enabled: inst.Enabled, RefundEnabled: inst.RefundEnabled, SortOrder: inst.SortOrder, } resp.Config = s.decryptAndMaskConfig(inst.Config) @@ -349,7 +368,8 @@ var validProviderKeys = map[string]bool{ } func (s *PaymentConfigService) CreateProviderInstance(ctx context.Context, req CreateProviderInstanceRequest) (*dbent.PaymentProviderInstance, error) { - if err := validateProviderRequest(req.ProviderKey, req.Name, req.SupportedTypes); err != nil { + typesStr := joinTypes(req.SupportedTypes) + if err := validateProviderRequest(req.ProviderKey, req.Name, typesStr); err != nil { return nil, err } enc, err := s.encryptConfig(req.Config) @@ -358,7 +378,7 @@ func (s *PaymentConfigService) CreateProviderInstance(ctx context.Context, req C } return s.entClient.PaymentProviderInstance.Create(). SetProviderKey(req.ProviderKey).SetName(req.Name).SetConfig(enc). - SetSupportedTypes(req.SupportedTypes).SetEnabled(req.Enabled). + SetSupportedTypes(typesStr).SetEnabled(req.Enabled). SetSortOrder(req.SortOrder).SetLimits(req.Limits).SetRefundEnabled(req.RefundEnabled). Save(ctx) } @@ -427,7 +447,7 @@ func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id in u.SetConfig(enc) } if req.SupportedTypes != nil { - u.SetSupportedTypes(*req.SupportedTypes) + u.SetSupportedTypes(joinTypes(req.SupportedTypes)) } if req.Enabled != nil { u.SetEnabled(*req.Enabled) diff --git a/frontend/src/components/payment/PaymentProviderDialog.vue b/frontend/src/components/payment/PaymentProviderDialog.vue index 7d82d639f5..d8bf1c509f 100644 --- a/frontend/src/components/payment/PaymentProviderDialog.vue +++ b/frontend/src/components/payment/PaymentProviderDialog.vue @@ -209,7 +209,6 @@ import { PROVIDER_SUPPORTED_TYPES, PROVIDER_CALLBACK_PATHS, WEBHOOK_PATHS, - parseTypes, getAvailableTypes, extractBaseUrl, } from './providerConfig' @@ -243,7 +242,7 @@ const { t } = useI18n() const form = reactive({ name: '', provider_key: 'easypay', - supported_types: '', + supported_types: [] as string[], enabled: true, refund_enabled: false, }) @@ -282,7 +281,7 @@ const resolvedFields = computed(() => { }) const limitableTypes = computed(() => { - const selected = parseTypes(form.supported_types).filter(t => t !== 'easypay') + const selected = form.supported_types.filter(t => t !== 'easypay') return selected.map(v => { const found = props.allPaymentTypes.find(pt => pt.value === v) return found || { value: v, label: v } @@ -291,18 +290,19 @@ const limitableTypes = computed(() => { // --- Methods --- function isTypeSelected(type: string): boolean { - return parseTypes(form.supported_types).includes(type) + return form.supported_types.includes(type) } function toggleType(type: string) { - const current = parseTypes(form.supported_types) - form.supported_types = current.includes(type) - ? current.filter(t => t !== type).join(',') - : [...current, type].join(',') + if (form.supported_types.includes(type)) { + form.supported_types = form.supported_types.filter(t => t !== type) + } else { + form.supported_types = [...form.supported_types, type] + } } function onKeyChange() { - form.supported_types = (PROVIDER_SUPPORTED_TYPES[form.provider_key] || []).join(',') + form.supported_types = [...(PROVIDER_SUPPORTED_TYPES[form.provider_key] || [])] clearConfig() applyDefaults() } @@ -357,7 +357,7 @@ function handleSave() { emitValidationError(t('admin.settings.payment.validationNameRequired')) return } - if (!form.supported_types.trim()) { + if (form.supported_types.length === 0) { emitValidationError(t('admin.settings.payment.validationTypesRequired')) return } @@ -408,7 +408,7 @@ function emitValidationError(msg: string) { function reset(defaultKey: string) { form.name = '' form.provider_key = defaultKey - form.supported_types = (PROVIDER_SUPPORTED_TYPES[defaultKey] || []).join(',') + form.supported_types = [...(PROVIDER_SUPPORTED_TYPES[defaultKey] || [])] form.enabled = true form.refund_enabled = false clearConfig() diff --git a/frontend/src/components/payment/ProviderCard.vue b/frontend/src/components/payment/ProviderCard.vue index 08bf1a3ef6..444bef5e36 100644 --- a/frontend/src/components/payment/ProviderCard.vue +++ b/frontend/src/components/payment/ProviderCard.vue @@ -66,7 +66,6 @@ import Icon from '@/components/icons/Icon.vue' import ToggleSwitch from './ToggleSwitch.vue' import type { ProviderInstance } from '@/types/payment' import type { TypeOption } from './providerConfig' -import { parseTypes } from './providerConfig' const PROVIDER_KEY_LABELS: Record = { easypay: 'admin.settings.payment.providerEasypay', @@ -93,6 +92,6 @@ const { t } = useI18n() const keyLabel = computed(() => t(PROVIDER_KEY_LABELS[props.provider.provider_key] || props.provider.provider_key)) function isSelected(type: string): boolean { - return parseTypes(props.provider.supported_types).includes(type) + return props.provider.supported_types.includes(type) } diff --git a/frontend/src/types/payment.ts b/frontend/src/types/payment.ts index 23442817a8..191fad23e8 100644 --- a/frontend/src/types/payment.ts +++ b/frontend/src/types/payment.ts @@ -104,7 +104,7 @@ export interface ProviderInstance { provider_key: string name: string config: Record - supported_types: string + supported_types: string[] enabled: boolean refund_enabled: boolean limits: string diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index d8f79c41c6..a1e414b1fd 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -2800,8 +2800,6 @@ async function saveBetaPolicySettings() { // ==================== Provider Management ==================== -import { parseTypes } from '@/components/payment/providerConfig' - const allPaymentTypes = computed(() => [ { value: 'easypay', label: t('payment.methods.easypay') }, { value: 'alipay', label: t('payment.methods.alipay') }, @@ -2901,18 +2899,16 @@ async function handleToggleField(provider: ProviderInstance, field: 'enabled' | } async function handleToggleType(provider: ProviderInstance, type: string) { - const current = parseTypes(provider.supported_types) - const updated = current.includes(type) - ? current.filter(t => t !== type) - : [...current, type] + const updated = provider.supported_types.includes(type) + ? provider.supported_types.filter(t => t !== type) + : [...provider.supported_types, type] if (updated.length === 0) { appStore.showError(t('admin.settings.payment.validationTypesRequired')) return } - const newVal = updated.join(',') try { - await adminAPI.payment.updateProvider(provider.id, { supportedTypes: newVal } as any) - provider.supported_types = newVal + await adminAPI.payment.updateProvider(provider.id, { supportedTypes: updated } as any) + provider.supported_types = updated } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'))) } }