diff --git a/controller/topup.go b/controller/topup.go
index a810eba760..579dad51df 100644
--- a/controller/topup.go
+++ b/controller/topup.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/QuantumNous/new-api/common"
+ "github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/service"
@@ -55,7 +56,9 @@ func GetTopUpInfo(c *gin.Context) {
"creem_products": setting.CreemProducts,
"pay_methods": payMethods,
"min_topup": operation_setting.MinTopUp,
+ "max_topup": operation_setting.GetPaymentSetting().MaxTopUp,
"stripe_min_topup": setting.StripeMinTopUp,
+ "stripe_max_topup": setting.StripeMaxTopUp,
"amount_options": operation_setting.GetPaymentSetting().AmountOptions,
"discount": operation_setting.GetPaymentSetting().AmountDiscount,
}
@@ -125,6 +128,19 @@ func getMinTopup() int64 {
return int64(minTopup)
}
+func getMaxTopup() int64 {
+ maxTopup := operation_setting.GetPaymentSetting().MaxTopUp
+ if maxTopup <= 0 {
+ return 0
+ }
+ if operation_setting.GetQuotaDisplayType() == operation_setting.QuotaDisplayTypeTokens {
+ dMaxTopup := decimal.NewFromInt(int64(maxTopup))
+ dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
+ maxTopup = int(dMaxTopup.Mul(dQuotaPerUnit).IntPart())
+ }
+ return int64(maxTopup)
+}
+
func RequestEpay(c *gin.Context) {
var req EpayRequest
err := c.ShouldBindJSON(&req)
@@ -133,7 +149,11 @@ func RequestEpay(c *gin.Context) {
return
}
if req.Amount < getMinTopup() {
- c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getMinTopup())})
+ c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooLow, map[string]any{"Min": getMinTopup()})})
+ return
+ }
+ if maxTopup := getMaxTopup(); maxTopup > 0 && req.Amount > maxTopup {
+ c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooHigh, map[string]any{"Max": maxTopup})})
return
}
@@ -321,7 +341,11 @@ func RequestAmount(c *gin.Context) {
}
if req.Amount < getMinTopup() {
- c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getMinTopup())})
+ c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooLow, map[string]any{"Min": getMinTopup()})})
+ return
+ }
+ if maxTopup := getMaxTopup(); maxTopup > 0 && req.Amount > maxTopup {
+ c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooHigh, map[string]any{"Max": maxTopup})})
return
}
id := c.GetInt("id")
diff --git a/controller/topup_stripe.go b/controller/topup_stripe.go
index e1718cc5ec..215b4f5a77 100644
--- a/controller/topup_stripe.go
+++ b/controller/topup_stripe.go
@@ -11,6 +11,7 @@ import (
"time"
"github.com/QuantumNous/new-api/common"
+ "github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting"
"github.com/QuantumNous/new-api/setting/operation_setting"
@@ -48,7 +49,11 @@ type StripeAdaptor struct {
func (*StripeAdaptor) RequestAmount(c *gin.Context, req *StripePayRequest) {
if req.Amount < getStripeMinTopup() {
- c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", getStripeMinTopup())})
+ c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooLow, map[string]any{"Min": getStripeMinTopup()})})
+ return
+ }
+ if maxTopup := getStripeMaxTopup(); maxTopup > 0 && req.Amount > maxTopup {
+ c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooHigh, map[string]any{"Max": maxTopup})})
return
}
id := c.GetInt("id")
@@ -71,11 +76,11 @@ func (*StripeAdaptor) RequestPay(c *gin.Context, req *StripePayRequest) {
return
}
if req.Amount < getStripeMinTopup() {
- c.JSON(200, gin.H{"message": fmt.Sprintf("充值数量不能小于 %d", getStripeMinTopup()), "data": 10})
+ c.JSON(200, gin.H{"message": i18n.T(c, i18n.MsgTopupAmountTooLow, map[string]any{"Min": getStripeMinTopup()}), "data": 10})
return
}
- if req.Amount > 10000 {
- c.JSON(200, gin.H{"message": "充值数量不能大于 10000", "data": 10})
+ if maxTopup := getStripeMaxTopup(); maxTopup > 0 && req.Amount > maxTopup {
+ c.JSON(200, gin.H{"message": i18n.T(c, i18n.MsgTopupAmountTooHigh, map[string]any{"Max": maxTopup}), "data": 10})
return
}
@@ -352,3 +357,14 @@ func getStripeMinTopup() int64 {
}
return int64(minTopup)
}
+
+func getStripeMaxTopup() int64 {
+ maxTopup := setting.StripeMaxTopUp
+ if maxTopup <= 0 {
+ return 0
+ }
+ if operation_setting.GetQuotaDisplayType() == operation_setting.QuotaDisplayTypeTokens {
+ maxTopup = maxTopup * int(common.QuotaPerUnit)
+ }
+ return int64(maxTopup)
+}
diff --git a/i18n/keys.go b/i18n/keys.go
index 4d98540a77..9bb599027b 100644
--- a/i18n/keys.go
+++ b/i18n/keys.go
@@ -147,6 +147,8 @@ const (
MsgTopupOrderStatus = "topup.order_status"
MsgTopupFailed = "topup.failed"
MsgTopupInvalidQuota = "topup.invalid_quota"
+ MsgTopupAmountTooLow = "topup.amount_too_low"
+ MsgTopupAmountTooHigh = "topup.amount_too_high"
)
// Channel related messages
diff --git a/i18n/locales/en.yaml b/i18n/locales/en.yaml
index 54dbf9181b..96f0998e0e 100644
--- a/i18n/locales/en.yaml
+++ b/i18n/locales/en.yaml
@@ -129,6 +129,8 @@ topup.order_not_exists: "Top-up order does not exist"
topup.order_status: "Top-up order status error"
topup.failed: "Top-up failed, please try again later"
topup.invalid_quota: "Invalid top-up quota"
+topup.amount_too_low: "Top-up amount cannot be less than {{.Min}}"
+topup.amount_too_high: "Top-up amount cannot be greater than {{.Max}}"
# Channel messages
channel.not_exists: "Channel does not exist"
diff --git a/i18n/locales/zh-CN.yaml b/i18n/locales/zh-CN.yaml
index 4e0b5cd15d..95c791a5e6 100644
--- a/i18n/locales/zh-CN.yaml
+++ b/i18n/locales/zh-CN.yaml
@@ -130,6 +130,8 @@ topup.order_not_exists: "充值订单不存在"
topup.order_status: "充值订单状态错误"
topup.failed: "充值失败,请稍后重试"
topup.invalid_quota: "无效的充值额度"
+topup.amount_too_low: "充值数量不能小于 {{.Min}}"
+topup.amount_too_high: "充值数量不能大于 {{.Max}}"
# Channel messages
channel.not_exists: "渠道不存在"
diff --git a/i18n/locales/zh-TW.yaml b/i18n/locales/zh-TW.yaml
index dcdd331b39..f3d0d1e16f 100644
--- a/i18n/locales/zh-TW.yaml
+++ b/i18n/locales/zh-TW.yaml
@@ -130,6 +130,8 @@ topup.order_not_exists: "充值訂單不存在"
topup.order_status: "充值訂單狀態錯誤"
topup.failed: "充值失敗,請稍後重試"
topup.invalid_quota: "無效的充值額度"
+topup.amount_too_low: "充值數量不能小於 {{.Min}}"
+topup.amount_too_high: "充值數量不能大於 {{.Max}}"
# Channel messages
channel.not_exists: "管道不存在"
diff --git a/model/option.go b/model/option.go
index 697e77dfe7..4cd0af883e 100644
--- a/model/option.go
+++ b/model/option.go
@@ -79,7 +79,9 @@ func InitOptionMap() {
common.OptionMap["Price"] = strconv.FormatFloat(operation_setting.Price, 'f', -1, 64)
common.OptionMap["USDExchangeRate"] = strconv.FormatFloat(operation_setting.USDExchangeRate, 'f', -1, 64)
common.OptionMap["MinTopUp"] = strconv.Itoa(operation_setting.MinTopUp)
+ common.OptionMap["MaxTopUp"] = strconv.Itoa(operation_setting.GetPaymentSetting().MaxTopUp)
common.OptionMap["StripeMinTopUp"] = strconv.Itoa(setting.StripeMinTopUp)
+ common.OptionMap["StripeMaxTopUp"] = strconv.Itoa(setting.StripeMaxTopUp)
common.OptionMap["StripeApiSecret"] = setting.StripeApiSecret
common.OptionMap["StripeWebhookSecret"] = setting.StripeWebhookSecret
common.OptionMap["StripePriceId"] = setting.StripePriceId
@@ -338,6 +340,9 @@ func updateOptionMap(key string, value string) (err error) {
operation_setting.USDExchangeRate, _ = strconv.ParseFloat(value, 64)
case "MinTopUp":
operation_setting.MinTopUp, _ = strconv.Atoi(value)
+ case "MaxTopUp":
+ val, _ := strconv.Atoi(value)
+ operation_setting.GetPaymentSetting().MaxTopUp = val
case "StripeApiSecret":
setting.StripeApiSecret = value
case "StripeWebhookSecret":
@@ -348,6 +353,8 @@ func updateOptionMap(key string, value string) (err error) {
setting.StripeUnitPrice, _ = strconv.ParseFloat(value, 64)
case "StripeMinTopUp":
setting.StripeMinTopUp, _ = strconv.Atoi(value)
+ case "StripeMaxTopUp":
+ setting.StripeMaxTopUp, _ = strconv.Atoi(value)
case "StripePromotionCodesEnabled":
setting.StripePromotionCodesEnabled = value == "true"
case "CreemApiKey":
diff --git a/setting/operation_setting/payment_setting.go b/setting/operation_setting/payment_setting.go
index 84162f4e52..ddd189bea5 100644
--- a/setting/operation_setting/payment_setting.go
+++ b/setting/operation_setting/payment_setting.go
@@ -5,12 +5,14 @@ import "github.com/QuantumNous/new-api/setting/config"
type PaymentSetting struct {
AmountOptions []int `json:"amount_options"`
AmountDiscount map[int]float64 `json:"amount_discount"` // 充值金额对应的折扣,例如 100 元 0.9 表示 100 元充值享受 9 折优惠
+ MaxTopUp int `json:"max_topup"` // 0 means no limit
}
// 默认配置
var paymentSetting = PaymentSetting{
AmountOptions: []int{10, 20, 50, 100, 200, 500},
AmountDiscount: map[int]float64{},
+ MaxTopUp: 0,
}
func init() {
diff --git a/setting/payment_stripe.go b/setting/payment_stripe.go
index d97120c852..d35e21eb92 100644
--- a/setting/payment_stripe.go
+++ b/setting/payment_stripe.go
@@ -5,4 +5,5 @@ var StripeWebhookSecret = ""
var StripePriceId = ""
var StripeUnitPrice = 8.0
var StripeMinTopUp = 1
+var StripeMaxTopUp = 0 // 0 means no limit
var StripePromotionCodesEnabled = false
diff --git a/web/src/components/topup/RechargeCard.jsx b/web/src/components/topup/RechargeCard.jsx
index 1ce0230986..e0adee8aad 100644
--- a/web/src/components/topup/RechargeCard.jsx
+++ b/web/src/components/topup/RechargeCard.jsx
@@ -66,6 +66,7 @@ const RechargeCard = ({
priceRatio,
topUpCount,
minTopUp,
+ maxTopUp,
renderQuotaWithAmount,
getAmount,
setTopUpCount,
@@ -238,11 +239,12 @@ const RechargeCard = ({
label={t('充值数量')}
disabled={!enableOnlineTopUp && !enableStripeTopUp}
placeholder={
- t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp)
+ maxTopUp > 0
+ ? t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp) + t(',最高 ') + renderQuotaWithAmount(maxTopUp)
+ : t('充值数量,最低 ') + renderQuotaWithAmount(minTopUp)
}
value={topUpCount}
min={minTopUp}
- max={999999999}
step={1}
precision={0}
onChange={async (value) => {
@@ -255,8 +257,8 @@ const RechargeCard = ({
onBlur={(e) => {
const value = parseInt(e.target.value);
if (!value || value < 1) {
- setTopUpCount(1);
- getAmount(1);
+ setTopUpCount(minTopUp);
+ getAmount(minTopUp);
}
}}
formatter={(value) => (value ? `${value}` : '')}
@@ -264,26 +266,33 @@ const RechargeCard = ({
value ? parseInt(value.replace(/[^\d]/g, '')) : 0
}
extraText={
-