diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go index d79a46ad6b..aac1cc8e26 100644 --- a/backend/internal/service/payment_config_service.go +++ b/backend/internal/service/payment_config_service.go @@ -343,38 +343,87 @@ func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req Upda return infraerrors.BadRequest("INVALID_RECHARGE_FEE_RATE", "recharge fee rate allows at most 2 decimal places") } } - m := map[string]string{ - SettingPaymentEnabled: formatBoolOrEmpty(req.Enabled), - SettingMinRechargeAmount: formatPositiveFloat(req.MinAmount), - SettingMaxRechargeAmount: formatPositiveFloat(req.MaxAmount), - SettingDailyRechargeLimit: formatPositiveFloat(req.DailyLimit), - SettingOrderTimeoutMinutes: formatPositiveInt(req.OrderTimeoutMin), - SettingMaxPendingOrders: formatPositiveInt(req.MaxPendingOrders), - SettingBalancePayDisabled: formatBoolOrEmpty(req.BalanceDisabled), - SettingBalanceRechargeMult: formatPositiveFloat(req.BalanceRechargeMultiplier), - SettingSubscriptionUSDToCNYRate: formatPositiveFloatExact(req.SubscriptionUSDToCNYRate), - SettingRechargeFeeRate: formatNonNegativeFloat(req.RechargeFeeRate), - SettingLoadBalanceStrategy: derefStr(req.LoadBalanceStrategy), - SettingProductNamePrefix: derefStr(req.ProductNamePrefix), - SettingProductNameSuffix: derefStr(req.ProductNameSuffix), - SettingHelpImageURL: derefStr(req.HelpImageURL), - SettingHelpText: derefStr(req.HelpText), - SettingCancelRateLimitOn: formatBoolOrEmpty(req.CancelRateLimitEnabled), - SettingCancelRateLimitMax: formatPositiveInt(req.CancelRateLimitMax), - SettingCancelWindowSize: formatPositiveInt(req.CancelRateLimitWindow), - SettingCancelWindowUnit: derefStr(req.CancelRateLimitUnit), - SettingCancelWindowMode: derefStr(req.CancelRateLimitMode), - SettingAlipayForceQRCode: formatBoolOrEmpty(req.AlipayForceQRCode), - SettingAlipayMobilePrecreateDeepLink: formatBoolOrEmpty(req.AlipayMobilePrecreateDeepLink), - SettingPaymentVisibleMethodAlipaySource: derefStr(req.VisibleMethodAlipaySource), - SettingPaymentVisibleMethodWxpaySource: derefStr(req.VisibleMethodWxpaySource), - SettingPaymentVisibleMethodAlipayEnabled: formatBoolOrEmpty(req.VisibleMethodAlipayEnabled), - SettingPaymentVisibleMethodWxpayEnabled: formatBoolOrEmpty(req.VisibleMethodWxpayEnabled), + m := make(map[string]string) + if req.Enabled != nil { + m[SettingPaymentEnabled] = formatBoolOrEmpty(req.Enabled) + } + if req.MinAmount != nil { + m[SettingMinRechargeAmount] = formatPositiveFloat(req.MinAmount) + } + if req.MaxAmount != nil { + m[SettingMaxRechargeAmount] = formatPositiveFloat(req.MaxAmount) + } + if req.DailyLimit != nil { + m[SettingDailyRechargeLimit] = formatPositiveFloat(req.DailyLimit) + } + if req.OrderTimeoutMin != nil { + m[SettingOrderTimeoutMinutes] = formatPositiveInt(req.OrderTimeoutMin) + } + if req.MaxPendingOrders != nil { + m[SettingMaxPendingOrders] = formatPositiveInt(req.MaxPendingOrders) } if req.EnabledTypes != nil { m[SettingEnabledPaymentTypes] = strings.Join(req.EnabledTypes, ",") - } else { - m[SettingEnabledPaymentTypes] = "" + } + if req.BalanceDisabled != nil { + m[SettingBalancePayDisabled] = formatBoolOrEmpty(req.BalanceDisabled) + } + if req.BalanceRechargeMultiplier != nil { + m[SettingBalanceRechargeMult] = formatPositiveFloat(req.BalanceRechargeMultiplier) + } + if req.SubscriptionUSDToCNYRate != nil { + m[SettingSubscriptionUSDToCNYRate] = formatPositiveFloatExact(req.SubscriptionUSDToCNYRate) + } + if req.RechargeFeeRate != nil { + m[SettingRechargeFeeRate] = formatNonNegativeFloat(req.RechargeFeeRate) + } + if req.LoadBalanceStrategy != nil { + m[SettingLoadBalanceStrategy] = derefStr(req.LoadBalanceStrategy) + } + if req.ProductNamePrefix != nil { + m[SettingProductNamePrefix] = derefStr(req.ProductNamePrefix) + } + if req.ProductNameSuffix != nil { + m[SettingProductNameSuffix] = derefStr(req.ProductNameSuffix) + } + if req.HelpImageURL != nil { + m[SettingHelpImageURL] = derefStr(req.HelpImageURL) + } + if req.HelpText != nil { + m[SettingHelpText] = derefStr(req.HelpText) + } + if req.CancelRateLimitEnabled != nil { + m[SettingCancelRateLimitOn] = formatBoolOrEmpty(req.CancelRateLimitEnabled) + } + if req.CancelRateLimitMax != nil { + m[SettingCancelRateLimitMax] = formatPositiveInt(req.CancelRateLimitMax) + } + if req.CancelRateLimitWindow != nil { + m[SettingCancelWindowSize] = formatPositiveInt(req.CancelRateLimitWindow) + } + if req.CancelRateLimitUnit != nil { + m[SettingCancelWindowUnit] = derefStr(req.CancelRateLimitUnit) + } + if req.CancelRateLimitMode != nil { + m[SettingCancelWindowMode] = derefStr(req.CancelRateLimitMode) + } + if req.AlipayForceQRCode != nil { + m[SettingAlipayForceQRCode] = formatBoolOrEmpty(req.AlipayForceQRCode) + } + if req.AlipayMobilePrecreateDeepLink != nil { + m[SettingAlipayMobilePrecreateDeepLink] = formatBoolOrEmpty(req.AlipayMobilePrecreateDeepLink) + } + if req.VisibleMethodAlipaySource != nil { + m[SettingPaymentVisibleMethodAlipaySource] = derefStr(req.VisibleMethodAlipaySource) + } + if req.VisibleMethodWxpaySource != nil { + m[SettingPaymentVisibleMethodWxpaySource] = derefStr(req.VisibleMethodWxpaySource) + } + if req.VisibleMethodAlipayEnabled != nil { + m[SettingPaymentVisibleMethodAlipayEnabled] = formatBoolOrEmpty(req.VisibleMethodAlipayEnabled) + } + if req.VisibleMethodWxpayEnabled != nil { + m[SettingPaymentVisibleMethodWxpayEnabled] = formatBoolOrEmpty(req.VisibleMethodWxpayEnabled) } return s.settingRepo.SetMultiple(ctx, m) } diff --git a/backend/internal/service/payment_config_service_test.go b/backend/internal/service/payment_config_service_test.go index 87b1794381..900866ef90 100644 --- a/backend/internal/service/payment_config_service_test.go +++ b/backend/internal/service/payment_config_service_test.go @@ -441,7 +441,12 @@ func (s *paymentConfigSettingRepoStub) GetAll(context.Context) (map[string]strin func (s *paymentConfigSettingRepoStub) Delete(context.Context, string) error { return nil } func TestUpdatePaymentConfig_PersistsVisibleMethodRouting(t *testing.T) { - repo := &paymentConfigSettingRepoStub{values: map[string]string{}} + repo := &paymentConfigSettingRepoStub{values: map[string]string{ + SettingPaymentVisibleMethodAlipayEnabled: "false", + SettingPaymentVisibleMethodAlipaySource: VisibleMethodSourceOfficialAlipay, + SettingPaymentVisibleMethodWxpayEnabled: "true", + SettingPaymentVisibleMethodWxpaySource: VisibleMethodSourceEasyPayWechat, + }} svc := &PaymentConfigService{settingRepo: repo} alipayEnabled := true @@ -470,6 +475,82 @@ func TestUpdatePaymentConfig_PersistsVisibleMethodRouting(t *testing.T) { } } +func TestUpdatePaymentConfig_OmittedVisibleMethodRoutingIsPreserved(t *testing.T) { + wantVisibleMethods := map[string]string{ + SettingPaymentVisibleMethodAlipayEnabled: "true", + SettingPaymentVisibleMethodAlipaySource: VisibleMethodSourceEasyPayAlipay, + SettingPaymentVisibleMethodWxpayEnabled: "false", + SettingPaymentVisibleMethodWxpaySource: VisibleMethodSourceOfficialWechat, + } + initial := make(map[string]string, len(wantVisibleMethods)) + for key, value := range wantVisibleMethods { + initial[key] = value + } + repo := &paymentConfigSettingRepoStub{values: initial} + svc := &PaymentConfigService{settingRepo: repo} + + enabled := true + err := svc.UpdatePaymentConfig(context.Background(), UpdatePaymentConfigRequest{Enabled: &enabled}) + if err != nil { + t.Fatalf("UpdatePaymentConfig returned error: %v", err) + } + + visibleMethodKeys := []string{ + SettingPaymentVisibleMethodAlipayEnabled, + SettingPaymentVisibleMethodAlipaySource, + SettingPaymentVisibleMethodWxpayEnabled, + SettingPaymentVisibleMethodWxpaySource, + } + for _, key := range visibleMethodKeys { + if _, ok := repo.updates[key]; ok { + t.Fatalf("omitted visible method setting %q was written", key) + } + if repo.values[key] != wantVisibleMethods[key] { + t.Fatalf("visible method setting %q = %q, want preserved value %q", key, repo.values[key], wantVisibleMethods[key]) + } + } + if repo.updates[SettingPaymentEnabled] != "true" { + t.Fatalf("payment enabled update = %q, want true", repo.updates[SettingPaymentEnabled]) + } +} + +func TestUpdatePaymentConfig_PersistsExplicitEmptyAndFalseValues(t *testing.T) { + repo := &paymentConfigSettingRepoStub{values: map[string]string{ + SettingEnabledPaymentTypes: "alipay,wxpay", + SettingBalancePayDisabled: "true", + SettingProductNamePrefix: "existing", + }} + svc := &PaymentConfigService{settingRepo: repo} + + falseValue := false + emptyString := "" + err := svc.UpdatePaymentConfig(context.Background(), UpdatePaymentConfigRequest{ + EnabledTypes: []string{}, + BalanceDisabled: &falseValue, + ProductNamePrefix: &emptyString, + }) + if err != nil { + t.Fatalf("UpdatePaymentConfig returned error: %v", err) + } + + want := map[string]string{ + SettingEnabledPaymentTypes: "", + SettingBalancePayDisabled: "false", + SettingProductNamePrefix: "", + } + if len(repo.updates) != len(want) { + t.Fatalf("updates = %v, want exactly %v", repo.updates, want) + } + for key, value := range want { + if repo.updates[key] != value { + t.Fatalf("update %q = %q, want %q", key, repo.updates[key], value) + } + if repo.values[key] != value { + t.Fatalf("stored %q = %q, want %q", key, repo.values[key], value) + } + } +} + func paymentConfigStrPtr(value string) *string { return &value }