diff --git a/backend/internal/handler/admin/payment_handler.go b/backend/internal/handler/admin/payment_handler.go index 5079a35c83..e21c6ca42c 100644 --- a/backend/internal/handler/admin/payment_handler.go +++ b/backend/internal/handler/admin/payment_handler.go @@ -221,7 +221,7 @@ func (h *PaymentHandler) DeletePlan(c *gin.Context) { // ListProviders returns all payment provider instances. // GET /api/v1/admin/payment/providers func (h *PaymentHandler) ListProviders(c *gin.Context) { - providers, err := h.configService.ListProviderInstances(c.Request.Context()) + providers, err := h.configService.ListProviderInstancesWithConfig(c.Request.Context()) if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go index 4681b2455f..efb6d8439d 100644 --- a/backend/internal/service/payment_config_service.go +++ b/backend/internal/service/payment_config_service.go @@ -244,6 +244,64 @@ func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*db return s.entClient.PaymentProviderInstance.Query().Order(paymentproviderinstance.BySortOrder()).All(ctx) } +// ProviderInstanceResponse is the API response for a provider instance. +// Config contains non-sensitive fields in cleartext; sensitive fields are masked. +type ProviderInstanceResponse struct { + ID int64 `json:"id"` + ProviderKey string `json:"provider_key"` + Name string `json:"name"` + Config map[string]string `json:"config"` + SupportedTypes string `json:"supported_types"` + Limits string `json:"limits"` + Enabled bool `json:"enabled"` + RefundEnabled bool `json:"refund_enabled"` + SortOrder int `json:"sort_order"` +} + +// ListProviderInstancesWithConfig returns provider instances with decrypted +// non-sensitive config fields and masked sensitive fields. +func (s *PaymentConfigService) ListProviderInstancesWithConfig(ctx context.Context) ([]ProviderInstanceResponse, error) { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Order(paymentproviderinstance.BySortOrder()).All(ctx) + if err != nil { + return nil, err + } + result := make([]ProviderInstanceResponse, 0, len(instances)) + for _, inst := range instances { + resp := ProviderInstanceResponse{ + ID: int64(inst.ID), ProviderKey: inst.ProviderKey, Name: inst.Name, + SupportedTypes: inst.SupportedTypes, Limits: inst.Limits, + Enabled: inst.Enabled, RefundEnabled: inst.RefundEnabled, SortOrder: inst.SortOrder, + } + resp.Config = s.decryptAndMaskConfig(inst.Config) + result = append(result, resp) + } + return result, nil +} + +func (s *PaymentConfigService) decryptAndMaskConfig(encrypted string) map[string]string { + if encrypted == "" { + return nil + } + decrypted, err := payment.Decrypt(encrypted, s.encryptionKey) + if err != nil { + return nil + } + var raw map[string]string + if err := json.Unmarshal([]byte(decrypted), &raw); err != nil { + return nil + } + masked := make(map[string]string, len(raw)) + for k, v := range raw { + if isSensitiveConfigField(k) && v != "" { + masked[k] = "••••••••" + } else { + masked[k] = v + } + } + return masked +} + // pendingOrderStatuses are order statuses considered "in progress" — // modifying provider credentials or deleting a provider/plan is blocked // while orders in these states exist. diff --git a/frontend/src/components/payment/PaymentProviderDialog.vue b/frontend/src/components/payment/PaymentProviderDialog.vue index 26fdcf7010..c7446a3f6e 100644 --- a/frontend/src/components/payment/PaymentProviderDialog.vue +++ b/frontend/src/components/payment/PaymentProviderDialog.vue @@ -349,6 +349,15 @@ function loadProvider(provider: ProviderInstance) { form.enabled = provider.enabled form.refund_enabled = provider.refund_enabled clearConfig() + // Pre-fill config from API response (non-sensitive in cleartext, sensitive masked) + if (provider.config) { + for (const [k, v] of Object.entries(provider.config)) { + // Skip masked sensitive values — leave field empty so user can re-enter + if (v !== '••••••••') { + config[k] = v + } + } + } // Parse existing limits if (provider.limits) { try { diff --git a/frontend/src/types/payment.ts b/frontend/src/types/payment.ts index b74a17f03b..a9a666b506 100644 --- a/frontend/src/types/payment.ts +++ b/frontend/src/types/payment.ts @@ -103,6 +103,7 @@ export interface ProviderInstance { id: number provider_key: string name: string + config: Record supported_types: string enabled: boolean refund_enabled: boolean