feat(payment): add recharge fee rate setting and fix provider card UI
- Add recharge_fee_rate system setting (percentage fee on top of recharge amount) - Full backend chain: config constant, PaymentConfig struct, update validation, read/write persistence, DTO, handler GET/PUT responses - Frontend: settings input with preview, i18n (zh/en), API types - Fix provider card toggle layout: labels above switches to save width - Fix Chinese translation: "EasyPay" → "易支付" in provider description
This commit is contained in:
@@ -189,6 +189,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
PaymentEnabledTypes: paymentCfg.EnabledTypes,
|
||||
PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
|
||||
PaymentBalanceRechargeMultiplier: paymentCfg.BalanceRechargeMultiplier,
|
||||
PaymentRechargeFeeRate: paymentCfg.RechargeFeeRate,
|
||||
PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
|
||||
PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
|
||||
PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
|
||||
@@ -327,6 +328,7 @@ type UpdateSettingsRequest struct {
|
||||
PaymentEnabledTypes []string `json:"payment_enabled_types"`
|
||||
PaymentBalanceDisabled *bool `json:"payment_balance_disabled"`
|
||||
PaymentBalanceRechargeMultiplier *float64 `json:"payment_balance_recharge_multiplier"`
|
||||
PaymentRechargeFeeRate *float64 `json:"payment_recharge_fee_rate"`
|
||||
PaymentLoadBalanceStrat *string `json:"payment_load_balance_strategy"`
|
||||
PaymentProductNamePrefix *string `json:"payment_product_name_prefix"`
|
||||
PaymentProductNameSuffix *string `json:"payment_product_name_suffix"`
|
||||
@@ -945,6 +947,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
EnabledTypes: req.PaymentEnabledTypes,
|
||||
BalanceDisabled: req.PaymentBalanceDisabled,
|
||||
BalanceRechargeMultiplier: req.PaymentBalanceRechargeMultiplier,
|
||||
RechargeFeeRate: req.PaymentRechargeFeeRate,
|
||||
LoadBalanceStrategy: req.PaymentLoadBalanceStrat,
|
||||
ProductNamePrefix: req.PaymentProductNamePrefix,
|
||||
ProductNameSuffix: req.PaymentProductNameSuffix,
|
||||
@@ -1086,6 +1089,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
PaymentEnabledTypes: updatedPaymentCfg.EnabledTypes,
|
||||
PaymentBalanceDisabled: updatedPaymentCfg.BalanceDisabled,
|
||||
PaymentBalanceRechargeMultiplier: updatedPaymentCfg.BalanceRechargeMultiplier,
|
||||
PaymentRechargeFeeRate: updatedPaymentCfg.RechargeFeeRate,
|
||||
PaymentLoadBalanceStrat: updatedPaymentCfg.LoadBalanceStrategy,
|
||||
PaymentProductNamePrefix: updatedPaymentCfg.ProductNamePrefix,
|
||||
PaymentProductNameSuffix: updatedPaymentCfg.ProductNameSuffix,
|
||||
@@ -1105,7 +1109,7 @@ func hasPaymentFields(req UpdateSettingsRequest) bool {
|
||||
req.PaymentMaxAmount != nil || req.PaymentDailyLimit != nil ||
|
||||
req.PaymentOrderTimeoutMin != nil || req.PaymentMaxPendingOrders != nil ||
|
||||
req.PaymentEnabledTypes != nil || req.PaymentBalanceDisabled != nil ||
|
||||
req.PaymentBalanceRechargeMultiplier != nil ||
|
||||
req.PaymentBalanceRechargeMultiplier != nil || req.PaymentRechargeFeeRate != nil ||
|
||||
req.PaymentLoadBalanceStrat != nil || req.PaymentProductNamePrefix != nil ||
|
||||
req.PaymentProductNameSuffix != nil || req.PaymentHelpImageURL != nil ||
|
||||
req.PaymentHelpText != nil || req.PaymentCancelRateLimitEnabled != nil ||
|
||||
|
||||
@@ -137,6 +137,7 @@ type SystemSettings struct {
|
||||
PaymentEnabledTypes []string `json:"payment_enabled_types"`
|
||||
PaymentBalanceDisabled bool `json:"payment_balance_disabled"`
|
||||
PaymentBalanceRechargeMultiplier float64 `json:"payment_balance_recharge_multiplier"`
|
||||
PaymentRechargeFeeRate float64 `json:"payment_recharge_fee_rate"`
|
||||
PaymentLoadBalanceStrat string `json:"payment_load_balance_strategy"`
|
||||
PaymentProductNamePrefix string `json:"payment_product_name_prefix"`
|
||||
PaymentProductNameSuffix string `json:"payment_product_name_suffix"`
|
||||
|
||||
@@ -132,6 +132,7 @@ func (h *PaymentHandler) GetCheckoutInfo(c *gin.Context) {
|
||||
Plans: planList,
|
||||
BalanceDisabled: cfg.BalanceDisabled,
|
||||
BalanceRechargeMultiplier: cfg.BalanceRechargeMultiplier,
|
||||
RechargeFeeRate: cfg.RechargeFeeRate,
|
||||
HelpText: cfg.HelpText,
|
||||
HelpImageURL: cfg.HelpImageURL,
|
||||
StripePublishableKey: cfg.StripePublishableKey,
|
||||
@@ -145,6 +146,7 @@ type checkoutInfoResponse struct {
|
||||
Plans []checkoutPlan `json:"plans"`
|
||||
BalanceDisabled bool `json:"balance_disabled"`
|
||||
BalanceRechargeMultiplier float64 `json:"balance_recharge_multiplier"`
|
||||
RechargeFeeRate float64 `json:"recharge_fee_rate"`
|
||||
HelpText string `json:"help_text"`
|
||||
HelpImageURL string `json:"help_image_url"`
|
||||
StripePublishableKey string `json:"stripe_publishable_key"`
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
SettingLoadBalanceStrategy = "LOAD_BALANCE_STRATEGY"
|
||||
SettingBalancePayDisabled = "BALANCE_PAYMENT_DISABLED"
|
||||
SettingBalanceRechargeMult = "BALANCE_RECHARGE_MULTIPLIER"
|
||||
SettingRechargeFeeRate = "RECHARGE_FEE_RATE"
|
||||
SettingProductNamePrefix = "PRODUCT_NAME_PREFIX"
|
||||
SettingProductNameSuffix = "PRODUCT_NAME_SUFFIX"
|
||||
SettingHelpImageURL = "PAYMENT_HELP_IMAGE_URL"
|
||||
@@ -52,6 +53,7 @@ type PaymentConfig struct {
|
||||
EnabledTypes []string `json:"enabled_payment_types"`
|
||||
BalanceDisabled bool `json:"balance_disabled"`
|
||||
BalanceRechargeMultiplier float64 `json:"balance_recharge_multiplier"`
|
||||
RechargeFeeRate float64 `json:"recharge_fee_rate"`
|
||||
LoadBalanceStrategy string `json:"load_balance_strategy"`
|
||||
ProductNamePrefix string `json:"product_name_prefix"`
|
||||
ProductNameSuffix string `json:"product_name_suffix"`
|
||||
@@ -78,6 +80,7 @@ type UpdatePaymentConfigRequest struct {
|
||||
EnabledTypes []string `json:"enabled_payment_types"`
|
||||
BalanceDisabled *bool `json:"balance_disabled"`
|
||||
BalanceRechargeMultiplier *float64 `json:"balance_recharge_multiplier"`
|
||||
RechargeFeeRate *float64 `json:"recharge_fee_rate"`
|
||||
LoadBalanceStrategy *string `json:"load_balance_strategy"`
|
||||
ProductNamePrefix *string `json:"product_name_prefix"`
|
||||
ProductNameSuffix *string `json:"product_name_suffix"`
|
||||
@@ -188,7 +191,7 @@ func (s *PaymentConfigService) GetPaymentConfig(ctx context.Context) (*PaymentCo
|
||||
keys := []string{
|
||||
SettingPaymentEnabled, SettingMinRechargeAmount, SettingMaxRechargeAmount,
|
||||
SettingDailyRechargeLimit, SettingOrderTimeoutMinutes, SettingMaxPendingOrders,
|
||||
SettingEnabledPaymentTypes, SettingBalancePayDisabled, SettingBalanceRechargeMult, SettingLoadBalanceStrategy,
|
||||
SettingEnabledPaymentTypes, SettingBalancePayDisabled, SettingBalanceRechargeMult, SettingRechargeFeeRate, SettingLoadBalanceStrategy,
|
||||
SettingProductNamePrefix, SettingProductNameSuffix,
|
||||
SettingHelpImageURL, SettingHelpText,
|
||||
SettingCancelRateLimitOn, SettingCancelRateLimitMax,
|
||||
@@ -214,6 +217,7 @@ func (s *PaymentConfigService) parsePaymentConfig(vals map[string]string) *Payme
|
||||
MaxPendingOrders: pcParseInt(vals[SettingMaxPendingOrders], defaultMaxPendingOrders),
|
||||
BalanceDisabled: vals[SettingBalancePayDisabled] == "true",
|
||||
BalanceRechargeMultiplier: normalizeBalanceRechargeMultiplier(pcParseFloat(vals[SettingBalanceRechargeMult], defaultBalanceRechargeMultiplier)),
|
||||
RechargeFeeRate: pcParseFloat(vals[SettingRechargeFeeRate], 0),
|
||||
LoadBalanceStrategy: vals[SettingLoadBalanceStrategy],
|
||||
ProductNamePrefix: vals[SettingProductNamePrefix],
|
||||
ProductNameSuffix: vals[SettingProductNameSuffix],
|
||||
@@ -267,6 +271,11 @@ func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req Upda
|
||||
return infraerrors.BadRequest("INVALID_BALANCE_RECHARGE_MULTIPLIER", "balance recharge multiplier must be greater than 0")
|
||||
}
|
||||
}
|
||||
if req.RechargeFeeRate != nil {
|
||||
if math.IsNaN(*req.RechargeFeeRate) || math.IsInf(*req.RechargeFeeRate, 0) || *req.RechargeFeeRate < 0 {
|
||||
return infraerrors.BadRequest("INVALID_RECHARGE_FEE_RATE", "recharge fee rate must be >= 0")
|
||||
}
|
||||
}
|
||||
m := map[string]string{
|
||||
SettingPaymentEnabled: formatBoolOrEmpty(req.Enabled),
|
||||
SettingMinRechargeAmount: formatPositiveFloat(req.MinAmount),
|
||||
@@ -276,6 +285,7 @@ func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req Upda
|
||||
SettingMaxPendingOrders: formatPositiveInt(req.MaxPendingOrders),
|
||||
SettingBalancePayDisabled: formatBoolOrEmpty(req.BalanceDisabled),
|
||||
SettingBalanceRechargeMult: formatPositiveFloat(req.BalanceRechargeMultiplier),
|
||||
SettingRechargeFeeRate: formatNonNegativeFloat(req.RechargeFeeRate),
|
||||
SettingLoadBalanceStrategy: derefStr(req.LoadBalanceStrategy),
|
||||
SettingProductNamePrefix: derefStr(req.ProductNamePrefix),
|
||||
SettingProductNameSuffix: derefStr(req.ProductNameSuffix),
|
||||
@@ -309,6 +319,13 @@ func formatPositiveFloat(v *float64) string {
|
||||
return strconv.FormatFloat(*v, 'f', 2, 64)
|
||||
}
|
||||
|
||||
func formatNonNegativeFloat(v *float64) string {
|
||||
if v == nil || *v < 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatFloat(*v, 'f', 2, 64)
|
||||
}
|
||||
|
||||
func formatPositiveInt(v *int) string {
|
||||
if v == nil || *v <= 0 {
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user