diff --git a/apis/admin/order.api b/apis/admin/order.api index 0d49074..87fa80d 100644 --- a/apis/admin/order.api +++ b/apis/admin/order.api @@ -14,14 +14,14 @@ type ( CreateOrderRequest { UserId int64 `json:"user_id" validate:"required"` Type uint8 `json:"type" validate:"required"` - Quantity int64 `json:"quantity,omitempty"` - Price int64 `json:"price" validate:"required"` - Amount int64 `json:"amount" validate:"required"` - Discount int64 `json:"discount,omitempty"` + Quantity int64 `json:"quantity,omitempty" validate:"omitempty,lte=1000"` + Price int64 `json:"price" validate:"required,gte=0,lte=2000000000"` + Amount int64 `json:"amount" validate:"required,gte=0,lte=2147483647"` + Discount int64 `json:"discount,omitempty" validate:"omitempty,gte=0,lte=2000000000"` Coupon string `json:"coupon,omitempty"` - CouponDiscount int64 `json:"coupon_discount,omitempty"` - Commission int64 `json:"commission"` - FeeAmount int64 `json:"fee_amount" validate:"required"` + CouponDiscount int64 `json:"coupon_discount,omitempty" validate:"omitempty,gte=0,lte=2000000000"` + Commission int64 `json:"commission" validate:"gte=0,lte=2000000000"` + FeeAmount int64 `json:"fee_amount" validate:"required,gte=0,lte=2000000000"` PaymentId int64 `json:"payment_id" validate:"required"` TradeNo string `json:"trade_no,omitempty"` Status uint8 `json:"status,omitempty"` diff --git a/apis/types.api b/apis/types.api index 780ced8..1fc1725 100644 --- a/apis/types.api +++ b/apis/types.api @@ -604,7 +604,7 @@ type ( //public order PurchaseOrderRequest { SubscribeId int64 `json:"subscribe_id"` - Quantity int64 `json:"quantity" validate:"required,gt=0"` + Quantity int64 `json:"quantity" validate:"required,gt=0,lte=1000"` Payment int64 `json:"payment,omitempty"` Coupon string `json:"coupon,omitempty"` } @@ -622,7 +622,7 @@ type ( } RenewalOrderRequest { UserSubscribeID int64 `json:"user_subscribe_id"` - Quantity int64 `json:"quantity"` + Quantity int64 `json:"quantity" validate:"lte=1000"` Payment int64 `json:"payment"` Coupon string `json:"coupon,omitempty"` } @@ -637,7 +637,7 @@ type ( OrderNo string `json:"order_no"` } RechargeOrderRequest { - Amount int64 `json:"amount"` + Amount int64 `json:"amount" validate:"required,gt=0,lte=2000000000"` Payment int64 `json:"payment"` } RechargeOrderResponse { diff --git a/initialize/currency.go b/initialize/currency.go index 25dc52f..29c5642 100644 --- a/initialize/currency.go +++ b/initialize/currency.go @@ -24,7 +24,7 @@ func Currency(ctx *svc.ServiceContext) { AccessKey string }{} tool.SystemConfigSliceReflectToStruct(currency, &configs) - + ctx.ExchangeRate = 0 // Default exchange rate to 0 ctx.Config.Currency = config.Currency{ Unit: configs.CurrencyUnit, Symbol: configs.CurrencySymbol, diff --git a/internal/logic/admin/system/updateCurrencyConfigLogic.go b/internal/logic/admin/system/updateCurrencyConfigLogic.go index 0104331..f49f490 100644 --- a/internal/logic/admin/system/updateCurrencyConfigLogic.go +++ b/internal/logic/admin/system/updateCurrencyConfigLogic.go @@ -4,6 +4,7 @@ import ( "context" "reflect" + "github.com/perfect-panel/server/initialize" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/model/system" "github.com/perfect-panel/server/pkg/tool" @@ -54,6 +55,7 @@ func (l *UpdateCurrencyConfigLogic) UpdateCurrencyConfig(req *types.CurrencyConf // clear cache return l.svcCtx.Redis.Del(l.ctx, config.CurrencyConfigKey, config.GlobalConfigKey).Err() }) + initialize.Currency(l.svcCtx) if err != nil { l.Errorw("[UpdateCurrencyConfig] update currency config error", logger.Field("error", err.Error())) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update invite config error: %v", err) diff --git a/internal/logic/public/order/constant.go b/internal/logic/public/order/constant.go index ca1c44a..cc3683a 100644 --- a/internal/logic/public/order/constant.go +++ b/internal/logic/public/order/constant.go @@ -6,4 +6,9 @@ const ( StripeAlipay = "stripe_alipay" StripeWeChatPay = "stripe_wechat_pay" Balance = "balance" + + // MaxOrderAmount Order amount limits + MaxOrderAmount = 2147483647 // int32 max value (2.1 billion) + MaxRechargeAmount = 2000000000 // 2 billion, slightly lower for safety + MaxQuantity = 1000 // Maximum quantity per order ) diff --git a/internal/logic/public/order/purchaseLogic.go b/internal/logic/public/order/purchaseLogic.go index 67769ea..d6ae038 100644 --- a/internal/logic/public/order/purchaseLogic.go +++ b/internal/logic/public/order/purchaseLogic.go @@ -58,6 +58,12 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P req.Quantity = 1 } + // Validate quantity limit + if req.Quantity > MaxQuantity { + l.Errorw("[Purchase] Quantity exceeds maximum limit", logger.Field("quantity", req.Quantity), logger.Field("max", MaxQuantity)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "quantity exceeds maximum limit of %d", MaxQuantity) + } + // find user subscription userSub, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id) if err != nil { @@ -97,6 +103,17 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P // discount amount amount := int64(float64(price) * discount) discountAmount := price - amount + + // Validate amount to prevent overflow + if amount > MaxOrderAmount { + l.Errorw("[Purchase] Order amount exceeds maximum limit", + logger.Field("amount", amount), + logger.Field("max", MaxOrderAmount), + logger.Field("user_id", u.Id), + logger.Field("subscribe_id", req.SubscribeId)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order amount exceeds maximum limit") + } + var coupon int64 = 0 // Calculate the coupon deduction if req.Coupon != "" { @@ -141,6 +158,15 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P if amount > 0 { feeAmount = calculateFee(amount, payment) amount += feeAmount + + // Final validation after adding fee + if amount > MaxOrderAmount { + l.Errorw("[Purchase] Final order amount exceeds maximum limit after fee", + logger.Field("amount", amount), + logger.Field("max", MaxOrderAmount), + logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order amount exceeds maximum limit") + } } // Calculate gift amount deduction after fee calculation var deductionAmount int64 diff --git a/internal/logic/public/order/rechargeLogic.go b/internal/logic/public/order/rechargeLogic.go index 04ff41c..a27053c 100644 --- a/internal/logic/public/order/rechargeLogic.go +++ b/internal/logic/public/order/rechargeLogic.go @@ -40,6 +40,21 @@ func (l *RechargeLogic) Recharge(req *types.RechargeOrderRequest) (resp *types.R logger.Error("current user is not found in context") return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } + + // Validate recharge amount + if req.Amount <= 0 { + l.Errorw("[Recharge] Invalid recharge amount", logger.Field("amount", req.Amount), logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "recharge amount must be greater than 0") + } + + if req.Amount > MaxRechargeAmount { + l.Errorw("[Recharge] Recharge amount exceeds maximum limit", + logger.Field("amount", req.Amount), + logger.Field("max", MaxRechargeAmount), + logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "recharge amount exceeds maximum limit") + } + // find payment method payment, err := l.svcCtx.PaymentModel.FindOne(l.ctx, req.Payment) if err != nil { @@ -48,6 +63,17 @@ func (l *RechargeLogic) Recharge(req *types.RechargeOrderRequest) (resp *types.R } // Calculate the handling fee feeAmount := calculateFee(req.Amount, payment) + totalAmount := req.Amount + feeAmount + + // Validate total amount after adding fee + if totalAmount > MaxOrderAmount { + l.Errorw("[Recharge] Total amount exceeds maximum limit after fee", + logger.Field("amount", totalAmount), + logger.Field("max", MaxOrderAmount), + logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "total amount exceeds maximum limit") + } + // query user is new purchase or renewal isNew, err := l.svcCtx.OrderModel.IsUserEligibleForNewOrder(l.ctx, u.Id) if err != nil { @@ -59,7 +85,7 @@ func (l *RechargeLogic) Recharge(req *types.RechargeOrderRequest) (resp *types.R OrderNo: tool.GenerateTradeNo(), Type: 4, Price: req.Amount, - Amount: req.Amount + feeAmount, + Amount: totalAmount, FeeAmount: feeAmount, PaymentId: payment.Id, Method: payment.Platform, diff --git a/internal/logic/public/order/renewalLogic.go b/internal/logic/public/order/renewalLogic.go index c78824f..e384413 100644 --- a/internal/logic/public/order/renewalLogic.go +++ b/internal/logic/public/order/renewalLogic.go @@ -50,6 +50,12 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene req.Quantity = 1 } + // Validate quantity limit + if req.Quantity > MaxQuantity { + l.Errorw("[Renewal] Quantity exceeds maximum limit", logger.Field("quantity", req.Quantity), logger.Field("max", MaxQuantity)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "quantity exceeds maximum limit of %d", MaxQuantity) + } + orderNo := tool.GenerateTradeNo() // find user subscribe userSubscribe, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeID) @@ -75,6 +81,17 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene price := sub.UnitPrice * req.Quantity amount := int64(float64(price) * discount) discountAmount := price - amount + + // Validate amount to prevent overflow + if amount > MaxOrderAmount { + l.Errorw("[Renewal] Order amount exceeds maximum limit", + logger.Field("amount", amount), + logger.Field("max", MaxOrderAmount), + logger.Field("user_id", u.Id), + logger.Field("subscribe_id", sub.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order amount exceeds maximum limit") + } + var coupon int64 = 0 if req.Coupon != "" { couponInfo, err := l.svcCtx.CouponModel.FindOneByCode(l.ctx, req.Coupon) @@ -134,6 +151,15 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene amount += feeAmount + // Final validation after adding fee + if amount > MaxOrderAmount { + l.Errorw("[Renewal] Final order amount exceeds maximum limit after fee", + logger.Field("amount", amount), + logger.Field("max", MaxOrderAmount), + logger.Field("user_id", u.Id)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "order amount exceeds maximum limit") + } + // create order orderInfo := order.Order{ UserId: u.Id, diff --git a/internal/logic/public/portal/purchaseCheckoutLogic.go b/internal/logic/public/portal/purchaseCheckoutLogic.go index 62c2c11..eb4c945 100644 --- a/internal/logic/public/portal/purchaseCheckoutLogic.go +++ b/internal/logic/public/portal/purchaseCheckoutLogic.go @@ -51,6 +51,7 @@ func NewPurchaseCheckoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) * // PurchaseCheckout processes the checkout for an order using the specified payment method // It validates the order, retrieves payment configuration, and routes to the appropriate payment handler func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest) (resp *types.CheckoutOrderResponse, err error) { + // Validate and retrieve order information orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) if err != nil { @@ -76,6 +77,7 @@ func (l *PurchaseCheckoutLogic) PurchaseCheckout(req *types.CheckoutOrderRequest // Process EPay payment - generates payment URL for redirect url, err := l.epayPayment(paymentConfig, orderInfo, req.ReturnUrl) if err != nil { + l.Logger.Error("[PurchaseCheckout] epay error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "epayPayment error: %v", err.Error()) } resp = &types.CheckoutOrderResponse{ @@ -274,6 +276,7 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order // Convert order amount to CNY using current exchange rate amount, err = l.queryExchangeRate("CNY", info.Amount) if err != nil { + l.Logger.Error("[PurchaseCheckout] queryExchangeRate error", logger.Field("error", err.Error())) return "", err } } else { @@ -382,6 +385,11 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount // Convert cents to decimal amount amount = float64(src) / float64(100) + // No conversion needed if target currency matches system currency + if to == l.svcCtx.Config.Currency.Unit { + return amount, nil + } + if l.svcCtx.ExchangeRate != 0 && to == "CNY" { amount = amount * l.svcCtx.ExchangeRate return amount, nil @@ -395,6 +403,7 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount // Convert currency if system currency differs from target currency result, err := exchangeRate.GetExchangeRete(l.svcCtx.Config.Currency.Unit, to, l.svcCtx.Config.Currency.AccessKey, 1) if err != nil { + l.Logger.Error("[PurchaseCheckout] QueryExchangeRate error", logger.Field("error", err.Error())) return 0, err } l.svcCtx.ExchangeRate = result diff --git a/internal/logic/server/constant.go b/internal/logic/server/constant.go index e2d1584..40a9113 100644 --- a/internal/logic/server/constant.go +++ b/internal/logic/server/constant.go @@ -24,6 +24,7 @@ type SecurityConfig struct { RealityPublicKey string `json:"reality_public_key"` RealityShortId string `json:"reality_short_id"` RealityMldsa65seed string `json:"reality_mldsa65seed"` + PaddingScheme string `json:"padding_scheme"` } type TransportConfig struct { diff --git a/internal/logic/server/getServerConfigLogic.go b/internal/logic/server/getServerConfigLogic.go index 94221a9..2b7ea3b 100644 --- a/internal/logic/server/getServerConfigLogic.go +++ b/internal/logic/server/getServerConfigLogic.go @@ -199,6 +199,7 @@ func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]inter RealityPrivateKey: config.RealityPrivateKey, RealityPublicKey: config.RealityPublicKey, RealityShortId: config.RealityShortId, + PaddingScheme: config.PaddingScheme, }, } case Tuic: diff --git a/internal/types/types.go b/internal/types/types.go index 70954a9..8238648 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -333,14 +333,14 @@ type CreateNodeRequest struct { type CreateOrderRequest struct { UserId int64 `json:"user_id" validate:"required"` Type uint8 `json:"type" validate:"required"` - Quantity int64 `json:"quantity,omitempty"` - Price int64 `json:"price" validate:"required"` - Amount int64 `json:"amount" validate:"required"` - Discount int64 `json:"discount,omitempty"` + Quantity int64 `json:"quantity,omitempty" validate:"omitempty,lte=1000"` + Price int64 `json:"price" validate:"required,gte=0,lte=2000000000"` + Amount int64 `json:"amount" validate:"required,gte=0,lte=2147483647"` + Discount int64 `json:"discount,omitempty" validate:"omitempty,gte=0,lte=2000000000"` Coupon string `json:"coupon,omitempty"` - CouponDiscount int64 `json:"coupon_discount,omitempty"` - Commission int64 `json:"commission"` - FeeAmount int64 `json:"fee_amount" validate:"required"` + CouponDiscount int64 `json:"coupon_discount,omitempty" validate:"omitempty,gte=0,lte=2000000000"` + Commission int64 `json:"commission" validate:"gte=0,lte=2000000000"` + FeeAmount int64 `json:"fee_amount" validate:"required,gte=0,lte=2000000000"` PaymentId int64 `json:"payment_id" validate:"required"` TradeNo string `json:"trade_no,omitempty"` Status uint8 `json:"status,omitempty"` @@ -1602,7 +1602,7 @@ type PubilcVerifyCodeConfig struct { type PurchaseOrderRequest struct { SubscribeId int64 `json:"subscribe_id"` - Quantity int64 `json:"quantity" validate:"required,gt=0"` + Quantity int64 `json:"quantity" validate:"required,gt=0,lte=1000"` Payment int64 `json:"payment,omitempty"` Coupon string `json:"coupon,omitempty"` } @@ -1814,7 +1814,7 @@ type QuotaTask struct { } type RechargeOrderRequest struct { - Amount int64 `json:"amount"` + Amount int64 `json:"amount" validate:"required,gt=0,lte=2000000000"` Payment int64 `json:"payment"` } @@ -1877,7 +1877,7 @@ type RegisterLog struct { type RenewalOrderRequest struct { UserSubscribeID int64 `json:"user_subscribe_id"` - Quantity int64 `json:"quantity"` + Quantity int64 `json:"quantity" validate:"lte=1000"` Payment int64 `json:"payment"` Coupon string `json:"coupon,omitempty"` } diff --git a/pkg/exchangeRate/exchangeRate.go b/pkg/exchangeRate/exchangeRate.go index 83ce545..8ee78c7 100644 --- a/pkg/exchangeRate/exchangeRate.go +++ b/pkg/exchangeRate/exchangeRate.go @@ -6,10 +6,11 @@ import ( "time" "github.com/go-resty/resty/v2" + "github.com/perfect-panel/server/pkg/logger" ) const ( - Url = "https://api.exchangerate.host" + Url = "https://api.apilayer.com" ) type Response struct { @@ -37,18 +38,20 @@ func GetExchangeRete(form, to, access string, amount float64) (float64, error) { amountStr := strconv.FormatFloat(amount, 'f', -1, 64) client.SetQueryParams(map[string]string{ - "from": form, - "to": to, - "amount": amountStr, - "access_key": access, + "from": form, + "to": to, + "amount": amountStr, }) - resp := new(Response) - _, err := client.R().SetResult(resp).Get("/convert") + result := new(Response) + resp, err := client.R().SetHeader("apikey", access).SetResult(result).Get("/currency_data/convert") + if err != nil { + return 0, err } - if !resp.Success { + if !result.Success { + logger.Info("Exchange Rate Response: ", resp.String()) return 0, errors.New("exchange rate failed") } - return resp.Result, nil + return result.Result, nil } diff --git a/pkg/exchangeRate/exchange_rate_test.go b/pkg/exchangeRate/exchange_rate_test.go index c24595d..1444098 100644 --- a/pkg/exchangeRate/exchange_rate_test.go +++ b/pkg/exchangeRate/exchange_rate_test.go @@ -4,7 +4,7 @@ import "testing" func TestGetExchangeRete(t *testing.T) { t.Skip("skip TestGetExchangeRete") - result, err := GetExchangeRete("USD", "CNY", "90734e5af4f5353114cdaf3bb9c3f2e3", 1) + result, err := GetExchangeRete("USD", "CNY", "", 1) if err != nil { t.Fatal(err) }