mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix(payment): full-replace config update + fix min/max defaults
- Payment config update is now full-replace (not patch): all fields sent every time - 0 values for min/max/daily = clear (use default: min=1, max=unlimited) - Payment page: provider-level limits override global, proper fallback chain - Fix quick amounts disappearing when global min/max is empty
This commit is contained in:
@@ -1 +1 @@
|
||||
0.1.108.42
|
||||
0.1.108.43
|
||||
|
||||
@@ -207,52 +207,56 @@ func (s *PaymentConfigService) parsePaymentConfig(vals map[string]string) *Payme
|
||||
// nil-check before serialisation — this is inherent to patch-style update patterns
|
||||
// and cannot be meaningfully decomposed without introducing unnecessary abstraction.
|
||||
func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req UpdatePaymentConfigRequest) error {
|
||||
m := make(map[string]string)
|
||||
if req.Enabled != nil {
|
||||
m[SettingPaymentEnabled] = strconv.FormatBool(*req.Enabled)
|
||||
}
|
||||
if req.MinAmount != nil {
|
||||
m[SettingMinRechargeAmount] = strconv.FormatFloat(*req.MinAmount, 'f', 2, 64)
|
||||
}
|
||||
if req.MaxAmount != nil {
|
||||
m[SettingMaxRechargeAmount] = strconv.FormatFloat(*req.MaxAmount, 'f', 2, 64)
|
||||
}
|
||||
if req.DailyLimit != nil {
|
||||
m[SettingDailyRechargeLimit] = strconv.FormatFloat(*req.DailyLimit, 'f', 2, 64)
|
||||
}
|
||||
if req.OrderTimeoutMin != nil {
|
||||
m[SettingOrderTimeoutMinutes] = strconv.Itoa(*req.OrderTimeoutMin)
|
||||
}
|
||||
if req.MaxPendingOrders != nil {
|
||||
m[SettingMaxPendingOrders] = strconv.Itoa(*req.MaxPendingOrders)
|
||||
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),
|
||||
SettingLoadBalanceStrategy: derefStr(req.LoadBalanceStrategy),
|
||||
SettingProductNamePrefix: derefStr(req.ProductNamePrefix),
|
||||
SettingProductNameSuffix: derefStr(req.ProductNameSuffix),
|
||||
SettingHelpImageURL: derefStr(req.HelpImageURL),
|
||||
SettingHelpText: derefStr(req.HelpText),
|
||||
}
|
||||
if req.EnabledTypes != nil {
|
||||
m[SettingEnabledPaymentTypes] = strings.Join(req.EnabledTypes, ",")
|
||||
}
|
||||
if req.BalanceDisabled != nil {
|
||||
m[SettingBalancePayDisabled] = strconv.FormatBool(*req.BalanceDisabled)
|
||||
}
|
||||
if req.LoadBalanceStrategy != nil {
|
||||
m[SettingLoadBalanceStrategy] = *req.LoadBalanceStrategy
|
||||
}
|
||||
if req.ProductNamePrefix != nil {
|
||||
m[SettingProductNamePrefix] = *req.ProductNamePrefix
|
||||
}
|
||||
if req.ProductNameSuffix != nil {
|
||||
m[SettingProductNameSuffix] = *req.ProductNameSuffix
|
||||
}
|
||||
if req.HelpImageURL != nil {
|
||||
m[SettingHelpImageURL] = *req.HelpImageURL
|
||||
}
|
||||
if req.HelpText != nil {
|
||||
m[SettingHelpText] = *req.HelpText
|
||||
}
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
} else {
|
||||
m[SettingEnabledPaymentTypes] = ""
|
||||
}
|
||||
return s.settingRepo.SetMultiple(ctx, m)
|
||||
}
|
||||
|
||||
func formatBoolOrEmpty(v *bool) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatBool(*v)
|
||||
}
|
||||
|
||||
func formatPositiveFloat(v *float64) string {
|
||||
if v == nil || *v <= 0 {
|
||||
return "" // empty → parsePaymentConfig uses default
|
||||
}
|
||||
return strconv.FormatFloat(*v, 'f', 2, 64)
|
||||
}
|
||||
|
||||
func formatPositiveInt(v *int) string {
|
||||
if v == nil || *v <= 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.Itoa(*v)
|
||||
}
|
||||
|
||||
func derefStr(v *string) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return *v
|
||||
}
|
||||
|
||||
// --- Provider Instance CRUD ---
|
||||
|
||||
func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*dbent.PaymentProviderInstance, error) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<AmountInput
|
||||
v-model="amount"
|
||||
:amounts="[10, 50, 100, 200, 500, 1000]"
|
||||
:min="config?.min_amount ?? 1"
|
||||
:min="minAmount"
|
||||
:max="maxAmount"
|
||||
/>
|
||||
<p v-if="amountError" class="mt-2 text-xs text-amber-600 dark:text-amber-300">{{ amountError }}</p>
|
||||
@@ -151,9 +151,15 @@ const tabs = computed(() => {
|
||||
})
|
||||
|
||||
const enabledMethods = computed(() => config.value?.enabled_payment_types ?? [])
|
||||
const minAmount = computed(() => {
|
||||
const limit = methodLimits.value[selectedMethod.value]
|
||||
if (limit?.single_min && limit.single_min > 0) return limit.single_min
|
||||
return config.value?.min_amount && config.value.min_amount > 0 ? config.value.min_amount : 1
|
||||
})
|
||||
const maxAmount = computed(() => {
|
||||
const limit = methodLimits.value[selectedMethod.value]
|
||||
return limit?.single_max && limit.single_max > 0 ? limit.single_max : (config.value?.max_amount ?? 10000)
|
||||
if (limit?.single_max && limit.single_max > 0) return limit.single_max
|
||||
return config.value?.max_amount && config.value.max_amount > 0 ? config.value.max_amount : 99999999
|
||||
})
|
||||
|
||||
const methodOptions = computed<PaymentMethodOption[]>(() =>
|
||||
@@ -178,16 +184,14 @@ const totalAmount = computed(() =>
|
||||
|
||||
const amountError = computed(() => {
|
||||
if (validAmount.value <= 0) return ''
|
||||
const min = config.value?.min_amount ?? 1
|
||||
if (validAmount.value < min) return t('payment.amountTooLow', { min })
|
||||
if (validAmount.value < minAmount.value) return t('payment.amountTooLow', { min: minAmount.value })
|
||||
if (validAmount.value > maxAmount.value) return t('payment.amountTooHigh', { max: maxAmount.value })
|
||||
return ''
|
||||
})
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
const min = config.value?.min_amount ?? 1
|
||||
const limitInfo = methodLimits.value[selectedMethod.value]
|
||||
return validAmount.value >= min && validAmount.value <= maxAmount.value && limitInfo?.available !== false
|
||||
return validAmount.value >= minAmount.value && validAmount.value <= maxAmount.value && limitInfo?.available !== false
|
||||
})
|
||||
|
||||
function openSubscribeDialog(plan: SubscriptionPlan) {
|
||||
|
||||
Reference in New Issue
Block a user