package treasury import ( "context" "strings" "time" "github.com/tech/sendico/gateway/tgsettle/internal/service/treasury/bot" "github.com/tech/sendico/gateway/tgsettle/internal/service/treasury/ledger" "github.com/tech/sendico/gateway/tgsettle/storage" storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" ) type Module struct { logger mlogger.Logger service *Service router *bot.Router scheduler *Scheduler ledger ledger.Client } func NewModule( logger mlogger.Logger, repo storage.TreasuryRequestsStore, users storage.TreasuryTelegramUsersStore, ledgerClient ledger.Client, cfg Config, send bot.SendTextFunc, ) (*Module, error) { if logger != nil { logger = logger.Named("treasury") } if users == nil { return nil, merrors.InvalidArgument("treasury telegram users store is required", "users") } service, err := NewService( logger, repo, ledgerClient, cfg.ExecutionDelay, cfg.MaxAmountPerOperation, cfg.MaxDailyAmount, ) if err != nil { return nil, err } module := &Module{ logger: logger, service: service, ledger: ledgerClient, } module.scheduler = NewScheduler(logger, service, NotifyFunc(send), cfg.PollInterval) module.router = bot.NewRouter(logger, &botServiceAdapter{svc: service}, send, module.scheduler, &botUsersAdapter{store: users}) return module, nil } func (m *Module) Enabled() bool { return m != nil && m.router != nil && m.router.Enabled() && m.scheduler != nil } func (m *Module) Start() { if m == nil || m.scheduler == nil { return } m.scheduler.Start() } func (m *Module) Shutdown() { if m == nil { return } if m.scheduler != nil { m.scheduler.Shutdown() } if m.ledger != nil { _ = m.ledger.Close() } } func (m *Module) HandleUpdate(ctx context.Context, update *model.TelegramWebhookUpdate) bool { if m == nil || m.router == nil { return false } return m.router.HandleUpdate(ctx, update) } type botServiceAdapter struct { svc *Service } type botUsersAdapter struct { store storage.TreasuryTelegramUsersStore } func (a *botUsersAdapter) ResolveUserBinding(ctx context.Context, telegramUserID string) (*bot.UserBinding, error) { if a == nil || a.store == nil { return nil, merrors.Internal("treasury users store unavailable") } record, err := a.store.FindByTelegramUserID(ctx, telegramUserID) if err != nil { return nil, err } if record == nil { return nil, nil } return &bot.UserBinding{ TelegramUserID: strings.TrimSpace(record.TelegramUserID), LedgerAccountID: strings.TrimSpace(record.LedgerAccountID), AllowedChatIDs: normalizeChatIDs(record.AllowedChatIDs), }, nil } func (a *botServiceAdapter) ExecutionDelay() (delay time.Duration) { if a == nil || a.svc == nil { return 0 } return a.svc.ExecutionDelay() } func (a *botServiceAdapter) MaxPerOperationLimit() string { if a == nil || a.svc == nil { return "" } return a.svc.MaxPerOperationLimit() } func (a *botServiceAdapter) GetActiveRequestForAccount(ctx context.Context, ledgerAccountID string) (*storagemodel.TreasuryRequest, error) { if a == nil || a.svc == nil { return nil, merrors.Internal("treasury service unavailable") } return a.svc.GetActiveRequestForAccount(ctx, ledgerAccountID) } func (a *botServiceAdapter) GetAccountProfile(ctx context.Context, ledgerAccountID string) (*bot.AccountProfile, error) { if a == nil || a.svc == nil { return nil, merrors.Internal("treasury service unavailable") } profile, err := a.svc.GetAccountProfile(ctx, ledgerAccountID) if err != nil { return nil, err } if profile == nil { return nil, nil } return &bot.AccountProfile{ AccountID: strings.TrimSpace(profile.AccountID), AccountCode: strings.TrimSpace(profile.AccountCode), Currency: strings.TrimSpace(profile.Currency), }, nil } func (a *botServiceAdapter) CreateRequest(ctx context.Context, input bot.CreateRequestInput) (*storagemodel.TreasuryRequest, error) { if a == nil || a.svc == nil { return nil, merrors.Internal("treasury service unavailable") } return a.svc.CreateRequest(ctx, CreateRequestInput{ OperationType: input.OperationType, TelegramUserID: input.TelegramUserID, LedgerAccountID: input.LedgerAccountID, ChatID: input.ChatID, Amount: input.Amount, }) } func (a *botServiceAdapter) ConfirmRequest(ctx context.Context, requestID string, telegramUserID string) (*storagemodel.TreasuryRequest, error) { if a == nil || a.svc == nil { return nil, merrors.Internal("treasury service unavailable") } return a.svc.ConfirmRequest(ctx, requestID, telegramUserID) } func (a *botServiceAdapter) CancelRequest(ctx context.Context, requestID string, telegramUserID string) (*storagemodel.TreasuryRequest, error) { if a == nil || a.svc == nil { return nil, merrors.Internal("treasury service unavailable") } return a.svc.CancelRequest(ctx, requestID, telegramUserID) } func normalizeChatIDs(values []string) []string { if len(values) == 0 { return nil } out := make([]string, 0, len(values)) seen := map[string]struct{}{} for _, next := range values { next = strings.TrimSpace(next) if next == "" { continue } if _, ok := seen[next]; ok { continue } seen[next] = struct{}{} out = append(out, next) } if len(out) == 0 { return nil } return out }