From 39f323d050335a848c0e4a55b27364fc12994b47 Mon Sep 17 00:00:00 2001 From: Stephan D Date: Thu, 11 Dec 2025 23:27:15 +0100 Subject: [PATCH] fixed currency pair validation --- .../api/srequest/payment_value_objects.go | 97 ++++++++----------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/api/server/interface/api/srequest/payment_value_objects.go b/api/server/interface/api/srequest/payment_value_objects.go index f360f12..5b35473 100644 --- a/api/server/interface/api/srequest/payment_value_objects.go +++ b/api/server/interface/api/srequest/payment_value_objects.go @@ -20,41 +20,17 @@ type AssetResolver interface { // Precompile regex for efficiency. var currencySyntax = regexp.MustCompile(`^[A-Z0-9]{2,10}$`) -func ValidateMoney(m *model.Money, assetResolver AssetResolver) error { - if m == nil { - return merrors.InvalidArgument("money is required", "intent.amount") - } - - // - // 1) Basic presence - // - if strings.TrimSpace(m.Amount) == "" { - return merrors.InvalidArgument("amount is required", "intent.amount") - } - if strings.TrimSpace(m.Currency) == "" { +// ValidateCurrency validates currency syntax and checks dictionary via assetResolver. +func ValidateCurrency(cur string, assetResolver AssetResolver) error { + // Basic presence + if strings.TrimSpace(cur) == "" { return merrors.InvalidArgument("currency is required", "intent.currency") } - // - // 2) Validate decimal amount - // - amount, err := decimal.NewFromString(m.Amount) - if err != nil { - return merrors.InvalidArgument("invalid decimal amount", "intent.amount") - } + // Normalize + cur = strings.ToUpper(strings.TrimSpace(cur)) - if amount.IsNegative() { - return merrors.InvalidArgument("amount must be >= 0", "intent.amount") - } - - // - // 3) Normalize currency - // - cur := strings.ToUpper(strings.TrimSpace(m.Currency)) - - // - // 4) Syntax validation first — reject malformed tickers early - // + // Syntax check if !currencySyntax.MatchString(cur) { return merrors.InvalidArgument( "invalid currency format (must be A–Z0–9, length 2–10)", @@ -62,9 +38,7 @@ func ValidateMoney(m *model.Money, assetResolver AssetResolver) error { ) } - // - // 5) Dictionary / environment validation - // + // Dictionary validation if assetResolver == nil { return merrors.InvalidArgument("asset resolver is not configured", "intent.currency") } @@ -76,37 +50,48 @@ func ValidateMoney(m *model.Money, assetResolver AssetResolver) error { return nil } +func ValidateMoney(m *model.Money, assetResolver AssetResolver) error { + if m == nil { + return merrors.InvalidArgument("money is required", "intent.amount") + } + + // 1) Basic presence + if strings.TrimSpace(m.Amount) == "" { + return merrors.InvalidArgument("amount is required", "intent.amount") + } + + // 2) Validate decimal amount + amount, err := decimal.NewFromString(m.Amount) + if err != nil { + return merrors.InvalidArgument("invalid decimal amount", "intent.amount") + } + if amount.IsNegative() { + return merrors.InvalidArgument("amount must be >= 0", "intent.amount") + } + + // 3) Validate currency via helper + if err := ValidateCurrency(m.Currency, assetResolver); err != nil { + return err + } + + return nil +} + type CurrencyPair struct { Base string `json:"base"` Quote string `json:"quote"` } func (p *CurrencyPair) Validate() error { - if p.Base == "" { - return merrors.InvalidArgument("base currency is required", "intent.fx.pair.base") + if p == nil { + return merrors.InvalidArgument("currency pair is required", "currncy_pair") } - if p.Quote == "" { - return merrors.InvalidArgument("quote currency is required", "intent.fx.pair.quote") + if err := ValidateCurrency(p.Base, nil); err != nil { + return merrors.InvalidArgument("invalid base currency in pair: "+err.Error(), "currency_pair.base") } - - if len(p.Base) != 3 { - return merrors.InvalidArgument("base currency must be 3 letters", "intent.fx.pair.base") + if err := ValidateCurrency(p.Quote, nil); err != nil { + return merrors.InvalidArgument("invalid quote currency in pair: "+err.Error(), "currency_pair.quote") } - if len(p.Quote) != 3 { - return merrors.InvalidArgument("quote currency must be 3 letters", "intent.fx.pair.quote") - } - - for _, c := range p.Base { - if c < 'A' || c > 'Z' { - return merrors.InvalidArgument("base currency must be uppercase A-Z", "intent.fx.pair.base") - } - } - for _, c := range p.Quote { - if c < 'A' || c > 'Z' { - return merrors.InvalidArgument("quote currency must be uppercase A-Z", "intent.fx.pair.quote") - } - } - return nil }