unified gateway interface

This commit is contained in:
Stephan D
2025-12-31 17:47:32 +01:00
parent 19b7b69bd8
commit 97ba7500dc
104 changed files with 8228 additions and 1742 deletions

View File

@@ -12,12 +12,14 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/tech/sendico/gateway/mntx/internal/appversion"
mntxservice "github.com/tech/sendico/gateway/mntx/internal/service/gateway"
"github.com/tech/sendico/gateway/mntx/internal/service/monetix"
"github.com/tech/sendico/pkg/api/routers"
"github.com/tech/sendico/pkg/merrors"
msg "github.com/tech/sendico/pkg/messaging"
"github.com/tech/sendico/pkg/mlogger"
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
"github.com/tech/sendico/pkg/server/grpcapp"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
@@ -28,14 +30,16 @@ type Imp struct {
file string
debug bool
config *config
app *grpcapp.App[struct{}]
http *http.Server
config *config
app *grpcapp.App[struct{}]
http *http.Server
service *mntxservice.Service
}
type config struct {
*grpcapp.Config `yaml:",inline"`
Monetix monetixConfig `yaml:"monetix"`
Gateway gatewayConfig `yaml:"gateway"`
HTTP httpConfig `yaml:"http"`
}
@@ -53,6 +57,33 @@ type monetixConfig struct {
StatusProcessing string `yaml:"status_processing"`
}
type gatewayConfig struct {
ID string `yaml:"id"`
Network string `yaml:"network"`
Currencies []string `yaml:"currencies"`
IsEnabled *bool `yaml:"is_enabled"`
Limits limitsConfig `yaml:"limits"`
}
type limitsConfig struct {
MinAmount string `yaml:"min_amount"`
MaxAmount string `yaml:"max_amount"`
PerTxMaxFee string `yaml:"per_tx_max_fee"`
PerTxMinAmount string `yaml:"per_tx_min_amount"`
PerTxMaxAmount string `yaml:"per_tx_max_amount"`
VolumeLimit map[string]string `yaml:"volume_limit"`
VelocityLimit map[string]int `yaml:"velocity_limit"`
CurrencyLimits map[string]limitsOverrideCfg `yaml:"currency_limits"`
}
type limitsOverrideCfg struct {
MaxVolume string `yaml:"max_volume"`
MinAmount string `yaml:"min_amount"`
MaxAmount string `yaml:"max_amount"`
MaxFee string `yaml:"max_fee"`
MaxOps int `yaml:"max_ops"`
}
type httpConfig struct {
Callback callbackConfig `yaml:"callback"`
}
@@ -86,6 +117,9 @@ func (i *Imp) Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if i.service != nil {
i.service.Shutdown()
}
if i.http != nil {
_ = i.http.Shutdown(ctx)
i.http = nil
@@ -131,6 +165,17 @@ func (i *Imp) Start() error {
zap.String("status_processing", monetixCfg.ProcessingStatus()),
)
gatewayDescriptor := resolveGatewayDescriptor(cfg.Gateway, monetixCfg)
if gatewayDescriptor != nil {
i.logger.Info("Gateway descriptor resolved",
zap.String("id", gatewayDescriptor.GetId()),
zap.String("rail", gatewayDescriptor.GetRail().String()),
zap.String("network", gatewayDescriptor.GetNetwork()),
zap.Int("currencies", len(gatewayDescriptor.GetCurrencies())),
zap.Bool("enabled", gatewayDescriptor.GetIsEnabled()),
)
}
i.logger.Info("Callback configuration resolved",
zap.String("address", callbackCfg.Address),
zap.String("path", callbackCfg.Path),
@@ -142,8 +187,10 @@ func (i *Imp) Start() error {
svc := mntxservice.NewService(logger,
mntxservice.WithProducer(producer),
mntxservice.WithMonetixConfig(monetixCfg),
mntxservice.WithGatewayDescriptor(gatewayDescriptor),
mntxservice.WithHTTPClient(&http.Client{Timeout: monetixCfg.Timeout()}),
)
i.service = svc
if err := i.startHTTPCallbackServer(svc, callbackCfg); err != nil {
return nil, err
@@ -243,6 +290,129 @@ func (i *Imp) resolveMonetixConfig(cfg monetixConfig) (monetix.Config, error) {
}, nil
}
func resolveGatewayDescriptor(cfg gatewayConfig, monetixCfg monetix.Config) *gatewayv1.GatewayInstanceDescriptor {
id := strings.TrimSpace(cfg.ID)
if id == "" {
id = "monetix"
}
network := strings.ToUpper(strings.TrimSpace(cfg.Network))
currencies := normalizeCurrencies(cfg.Currencies)
if len(currencies) == 0 {
currencies = normalizeCurrencies(monetixCfg.AllowedCurrencies)
}
enabled := true
if cfg.IsEnabled != nil {
enabled = *cfg.IsEnabled
}
limits := buildGatewayLimits(cfg.Limits)
if limits == nil {
limits = &gatewayv1.Limits{MinAmount: "0"}
}
version := strings.TrimSpace(appversion.Version)
return &gatewayv1.GatewayInstanceDescriptor{
Id: id,
Rail: gatewayv1.Rail_RAIL_CARD_PAYOUT,
Network: network,
Currencies: currencies,
Capabilities: &gatewayv1.RailCapabilities{
CanPayOut: true,
CanPayIn: false,
CanReadBalance: false,
CanSendFee: false,
RequiresObserveConfirm: false,
},
Limits: limits,
Version: version,
IsEnabled: enabled,
}
}
func normalizeCurrencies(values []string) []string {
if len(values) == 0 {
return nil
}
seen := map[string]bool{}
result := make([]string, 0, len(values))
for _, value := range values {
clean := strings.ToUpper(strings.TrimSpace(value))
if clean == "" || seen[clean] {
continue
}
seen[clean] = true
result = append(result, clean)
}
return result
}
func buildGatewayLimits(cfg limitsConfig) *gatewayv1.Limits {
hasValue := strings.TrimSpace(cfg.MinAmount) != "" ||
strings.TrimSpace(cfg.MaxAmount) != "" ||
strings.TrimSpace(cfg.PerTxMaxFee) != "" ||
strings.TrimSpace(cfg.PerTxMinAmount) != "" ||
strings.TrimSpace(cfg.PerTxMaxAmount) != "" ||
len(cfg.VolumeLimit) > 0 ||
len(cfg.VelocityLimit) > 0 ||
len(cfg.CurrencyLimits) > 0
if !hasValue {
return nil
}
limits := &gatewayv1.Limits{
MinAmount: strings.TrimSpace(cfg.MinAmount),
MaxAmount: strings.TrimSpace(cfg.MaxAmount),
PerTxMaxFee: strings.TrimSpace(cfg.PerTxMaxFee),
PerTxMinAmount: strings.TrimSpace(cfg.PerTxMinAmount),
PerTxMaxAmount: strings.TrimSpace(cfg.PerTxMaxAmount),
}
if len(cfg.VolumeLimit) > 0 {
limits.VolumeLimit = map[string]string{}
for key, value := range cfg.VolumeLimit {
bucket := strings.TrimSpace(key)
amount := strings.TrimSpace(value)
if bucket == "" || amount == "" {
continue
}
limits.VolumeLimit[bucket] = amount
}
}
if len(cfg.VelocityLimit) > 0 {
limits.VelocityLimit = map[string]int32{}
for key, value := range cfg.VelocityLimit {
bucket := strings.TrimSpace(key)
if bucket == "" {
continue
}
limits.VelocityLimit[bucket] = int32(value)
}
}
if len(cfg.CurrencyLimits) > 0 {
limits.CurrencyLimits = map[string]*gatewayv1.LimitsOverride{}
for key, override := range cfg.CurrencyLimits {
currency := strings.ToUpper(strings.TrimSpace(key))
if currency == "" {
continue
}
limits.CurrencyLimits[currency] = &gatewayv1.LimitsOverride{
MaxVolume: strings.TrimSpace(override.MaxVolume),
MinAmount: strings.TrimSpace(override.MinAmount),
MaxAmount: strings.TrimSpace(override.MaxAmount),
MaxFee: strings.TrimSpace(override.MaxFee),
MaxOps: int32(override.MaxOps),
}
}
}
return limits
}
type callbackRuntimeConfig struct {
Address string
Path string