linting
This commit is contained in:
47
api/gateway/tgsettle/.golangci.yml
Normal file
47
api/gateway/tgsettle/.golangci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
version: "2"
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- bodyclose
|
||||
- canonicalheader
|
||||
- copyloopvar
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- wastedassign
|
||||
disable:
|
||||
- depguard
|
||||
- exhaustruct
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gomoddirectives
|
||||
- wrapcheck
|
||||
- cyclop
|
||||
- dupl
|
||||
- funlen
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- ireturn
|
||||
- lll
|
||||
- mnd
|
||||
- nestif
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- varnamelen
|
||||
- wsl_v5
|
||||
@@ -217,7 +217,7 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe
|
||||
ReplyToMessageID: message.MessageID,
|
||||
Text: "Only approved users can confirm this payment.",
|
||||
}); e != nil {
|
||||
s.logger.Warn("Failed to create telegram text", append(replyFields, zap.Error(err))...)
|
||||
s.logger.Warn("Failed to create telegram text", append(replyFields, zap.Error(e))...)
|
||||
}
|
||||
if err := s.clearPendingConfirmation(ctx, pending.RequestID); err != nil {
|
||||
return err
|
||||
@@ -242,7 +242,7 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe
|
||||
ReplyToMessageID: message.MessageID,
|
||||
Text: clarificationMessage(reason),
|
||||
}); e != nil {
|
||||
s.logger.Warn("Failed to create telegram text", append(replyFields, zap.Error(err))...)
|
||||
s.logger.Warn("Failed to create telegram text", append(replyFields, zap.Error(e))...)
|
||||
}
|
||||
s.logger.Warn("Telegram confirmation reply dropped",
|
||||
append(replyFields,
|
||||
@@ -307,13 +307,13 @@ func (s *Service) publishPendingConfirmationResult(pending *storagemodel.Pending
|
||||
}
|
||||
sourceService := strings.TrimSpace(pending.SourceService)
|
||||
if sourceService == "" {
|
||||
sourceService = string(mservice.PaymentGateway)
|
||||
sourceService = mservice.PaymentGateway
|
||||
}
|
||||
rail := strings.TrimSpace(pending.Rail)
|
||||
if rail == "" {
|
||||
rail = s.rail
|
||||
}
|
||||
env := confirmations.ConfirmationResult(string(mservice.PaymentGateway), result, sourceService, rail)
|
||||
env := confirmations.ConfirmationResult(mservice.PaymentGateway, result, sourceService, rail)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish confirmation result", zap.Error(err),
|
||||
zap.String("request_id", strings.TrimSpace(result.RequestID)),
|
||||
@@ -338,7 +338,7 @@ func (s *Service) sendTelegramText(_ context.Context, request *model.TelegramTex
|
||||
if request.ChatID == "" || request.Text == "" {
|
||||
return merrors.InvalidArgument("telegram chat_id and text are required", "chat_id", "text")
|
||||
}
|
||||
env := tnotifications.TelegramText(string(mservice.PaymentGateway), request)
|
||||
env := tnotifications.TelegramText(mservice.PaymentGateway, request)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish telegram text request", zap.Error(err),
|
||||
zap.String("request_id", request.RequestID),
|
||||
|
||||
@@ -31,7 +31,7 @@ func (s *Service) GetCapabilities(_ context.Context, _ *connectorv1.GetCapabilit
|
||||
}
|
||||
|
||||
func (s *Service) OpenAccount(_ context.Context, _ *connectorv1.OpenAccountRequest) (*connectorv1.OpenAccountResponse, error) {
|
||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND, "open_account: unsupported", nil, "")}, nil
|
||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND, "open_account: unsupported", nil)}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetAccount(_ context.Context, _ *connectorv1.GetAccountRequest) (*connectorv1.GetAccountResponse, error) {
|
||||
@@ -49,16 +49,16 @@ func (s *Service) GetBalance(_ context.Context, _ *connectorv1.GetBalanceRequest
|
||||
func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOperationRequest) (*connectorv1.SubmitOperationResponse, error) {
|
||||
if req == nil || req.GetOperation() == nil {
|
||||
s.logger.Warn("Submit operation rejected", zap.String("reason", "operation is required"))
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: operation is required", nil, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: operation is required", nil)}}, nil
|
||||
}
|
||||
op := req.GetOperation()
|
||||
if strings.TrimSpace(op.GetIdempotencyKey()) == "" {
|
||||
s.logger.Warn("Submit operation rejected", append(operationLogFields(op), zap.String("reason", "idempotency_key is required"))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: idempotency_key is required", op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: idempotency_key is required", op)}}, nil
|
||||
}
|
||||
if op.GetType() != connectorv1.OperationType_TRANSFER {
|
||||
s.logger.Warn("Submit operation rejected", append(operationLogFields(op), zap.String("reason", "unsupported operation type"))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op)}}, nil
|
||||
}
|
||||
reader := params.New(op.GetParams())
|
||||
metadata := reader.StringMap("metadata")
|
||||
@@ -74,22 +74,22 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
}
|
||||
if paymentIntentID == "" {
|
||||
s.logger.Warn("Submit operation rejected", append(operationLogFields(op), zap.String("reason", "payment_intent_id is required"))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: payment_intent_id is required", op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: payment_intent_id is required", op)}}, nil
|
||||
}
|
||||
source := operationAccountID(op.GetFrom())
|
||||
if source == "" {
|
||||
s.logger.Warn("Submit operation rejected", append(operationLogFields(op), zap.String("reason", "from.account is required"))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: from.account is required", op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: from.account is required", op)}}, nil
|
||||
}
|
||||
dest, err := transferDestinationFromOperation(op)
|
||||
if err != nil {
|
||||
s.logger.Warn("Submit operation rejected", append(operationLogFields(op), zap.Error(err))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op)}}, nil
|
||||
}
|
||||
amount := op.GetMoney()
|
||||
if amount == nil {
|
||||
s.logger.Warn("Submit operation rejected", append(operationLogFields(op), zap.String("reason", "money is required"))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: money is required", op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: money is required", op)}}, nil
|
||||
}
|
||||
|
||||
metadata[metadataPaymentIntentID] = paymentIntentID
|
||||
@@ -133,7 +133,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Warn("Submit operation transfer failed", append(logFields, zap.Error(err))...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op)}}, nil
|
||||
}
|
||||
transfer := resp.GetTransfer()
|
||||
operationID := strings.TrimSpace(transfer.GetOperationRef())
|
||||
@@ -142,7 +142,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
zap.String("transfer_ref", strings.TrimSpace(transfer.GetTransferRef())),
|
||||
)...)
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{
|
||||
Error: connectorError(connectorv1.ErrorCode_TEMPORARY_UNAVAILABLE, "submit_operation: operation_ref is missing in transfer response", op, ""),
|
||||
Error: connectorError(connectorv1.ErrorCode_TEMPORARY_UNAVAILABLE, "submit_operation: operation_ref is missing in transfer response", op),
|
||||
}}, nil
|
||||
}
|
||||
s.logger.Info("Submit operation transfer submitted", append(logFields,
|
||||
@@ -361,11 +361,10 @@ func transferDestinationLogFields(dest *chainv1.TransferDestination) []zap.Field
|
||||
}
|
||||
}
|
||||
|
||||
func connectorError(code connectorv1.ErrorCode, message string, op *connectorv1.Operation, accountID string) *connectorv1.ConnectorError {
|
||||
func connectorError(code connectorv1.ErrorCode, message string, op *connectorv1.Operation) *connectorv1.ConnectorError {
|
||||
err := &connectorv1.ConnectorError{
|
||||
Code: code,
|
||||
Message: strings.TrimSpace(message),
|
||||
AccountId: strings.TrimSpace(accountID),
|
||||
Code: code,
|
||||
Message: strings.TrimSpace(message),
|
||||
}
|
||||
if op != nil {
|
||||
err.CorrelationId = strings.TrimSpace(op.GetCorrelationId())
|
||||
|
||||
@@ -24,15 +24,15 @@ func (s *Service) outboxStore() gatewayoutbox.Store {
|
||||
return provider.Outbox()
|
||||
}
|
||||
|
||||
func (s *Service) startOutboxReliableProducer() error {
|
||||
func (s *Service) startOutboxReliableProducer(_ context.Context) error {
|
||||
if s == nil || s.repo == nil {
|
||||
return nil
|
||||
}
|
||||
return s.outbox.Start(s.logger, s.producer, s.outboxStore(), s.msgCfg)
|
||||
return s.outbox.Start(s.logger, s.producer, s.outboxStore(), s.msgCfg) //nolint:contextcheck // Reliable runtime start API does not accept context.
|
||||
}
|
||||
|
||||
func (s *Service) sendWithOutbox(ctx context.Context, env me.Envelope) error {
|
||||
if err := s.startOutboxReliableProducer(); err != nil {
|
||||
if err := s.startOutboxReliableProducer(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.outbox.Send(ctx, env)
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
tnotifications "github.com/tech/sendico/pkg/messaging/notifications/telegram"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
@@ -63,7 +62,7 @@ type Config struct {
|
||||
AcceptedUserIDs []string
|
||||
SuccessReaction string
|
||||
InvokeURI string
|
||||
MessagingSettings pmodel.SettingsT
|
||||
MessagingSettings model.SettingsT
|
||||
DiscoveryRegistry *discovery.Registry
|
||||
Treasury TreasuryConfig
|
||||
}
|
||||
@@ -90,7 +89,7 @@ type Service struct {
|
||||
producer msg.Producer
|
||||
broker mb.Broker
|
||||
cfg Config
|
||||
msgCfg pmodel.SettingsT
|
||||
msgCfg model.SettingsT
|
||||
rail string
|
||||
chatID string
|
||||
announcer *discovery.Announcer
|
||||
@@ -131,7 +130,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
||||
if svc.successReaction == "" {
|
||||
svc.successReaction = defaultTelegramSuccessReaction
|
||||
}
|
||||
if err := svc.startOutboxReliableProducer(); err != nil {
|
||||
if err := svc.startOutboxReliableProducer(context.Background()); err != nil {
|
||||
svc.logger.Warn("Failed to initialise outbox reliable producer", zap.Error(err))
|
||||
}
|
||||
svc.startConsumers()
|
||||
@@ -240,9 +239,9 @@ func (s *Service) startConsumers() {
|
||||
}
|
||||
return
|
||||
}
|
||||
resultProcessor := confirmations.NewConfirmationResultProcessor(s.logger, string(mservice.PaymentGateway), s.rail, s.onConfirmationResult)
|
||||
resultProcessor := confirmations.NewConfirmationResultProcessor(s.logger, mservice.PaymentGateway, s.rail, s.onConfirmationResult)
|
||||
s.consumeProcessor(resultProcessor)
|
||||
dispatchProcessor := confirmations.NewConfirmationDispatchProcessor(s.logger, string(mservice.PaymentGateway), s.rail, s.onConfirmationDispatch)
|
||||
dispatchProcessor := confirmations.NewConfirmationDispatchProcessor(s.logger, mservice.PaymentGateway, s.rail, s.onConfirmationDispatch)
|
||||
s.consumeProcessor(dispatchProcessor)
|
||||
updateProcessor := tnotifications.NewTelegramUpdateProcessor(s.logger, s.onTelegramUpdate)
|
||||
s.consumeProcessor(updateProcessor)
|
||||
@@ -277,7 +276,7 @@ func (s *Service) SubmitTransfer(ctx context.Context, req *chainv1.SubmitTransfe
|
||||
s.logger.Warn("Submit transfer rejected", zap.String("reason", "amount is required"), zap.String("idempotency_key", idempotencyKey))
|
||||
return nil, merrors.InvalidArgument("submit_transfer: amount is required")
|
||||
}
|
||||
intent, err := intentFromSubmitTransfer(req, s.rail, s.chatID)
|
||||
intent, err := intentFromSubmitTransfer(req, s.rail)
|
||||
if err != nil {
|
||||
s.logger.Warn("Submit transfer rejected", zap.Error(err), zap.String("idempotency_key", idempotencyKey))
|
||||
return nil, err
|
||||
@@ -537,7 +536,7 @@ func (s *Service) buildConfirmationRequest(intent *model.PaymentGatewayIntent) (
|
||||
QuoteRef: intent.QuoteRef,
|
||||
AcceptedUserIDs: s.cfg.AcceptedUserIDs,
|
||||
TimeoutSeconds: timeout,
|
||||
SourceService: string(mservice.PaymentGateway),
|
||||
SourceService: mservice.PaymentGateway,
|
||||
Rail: rail,
|
||||
OperationRef: intent.OperationRef,
|
||||
IntentRef: intent.IntentRef,
|
||||
@@ -554,7 +553,7 @@ func (s *Service) sendConfirmationRequest(request *model.ConfirmationRequest) er
|
||||
s.logger.Warn("Messaging producer not configured")
|
||||
return merrors.Internal("messaging producer is not configured")
|
||||
}
|
||||
env := confirmations.ConfirmationRequest(string(mservice.PaymentGateway), request)
|
||||
env := confirmations.ConfirmationRequest(mservice.PaymentGateway, request)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish confirmation request",
|
||||
zap.Error(err),
|
||||
@@ -590,7 +589,7 @@ func (s *Service) publishTelegramReaction(result *model.ConfirmationResult) {
|
||||
if request.ChatID == "" || request.MessageID == "" || request.Emoji == "" {
|
||||
return
|
||||
}
|
||||
env := tnotifications.TelegramReaction(string(mservice.PaymentGateway), request)
|
||||
env := tnotifications.TelegramReaction(mservice.PaymentGateway, request)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish telegram reaction",
|
||||
zap.Error(err),
|
||||
@@ -632,7 +631,7 @@ func (s *Service) startAnnouncer() {
|
||||
Operations: caps,
|
||||
InvokeURI: s.invokeURI,
|
||||
}
|
||||
s.announcer = discovery.NewAnnouncer(s.logger, s.producer, string(mservice.PaymentGateway), announce)
|
||||
s.announcer = discovery.NewAnnouncer(s.logger, s.producer, mservice.PaymentGateway, announce)
|
||||
s.announcer.Start()
|
||||
}
|
||||
|
||||
@@ -685,7 +684,7 @@ func paymentRecordFromIntent(intent *model.PaymentGatewayIntent, confirmReq *mod
|
||||
return record
|
||||
}
|
||||
|
||||
func intentFromSubmitTransfer(req *chainv1.SubmitTransferRequest, defaultRail, defaultChatID string) (*model.PaymentGatewayIntent, error) {
|
||||
func intentFromSubmitTransfer(req *chainv1.SubmitTransferRequest, defaultRail string) (*model.PaymentGatewayIntent, error) {
|
||||
if req == nil {
|
||||
return nil, merrors.InvalidArgument("submit_transfer: request is required")
|
||||
}
|
||||
@@ -733,14 +732,10 @@ func intentFromSubmitTransfer(req *chainv1.SubmitTransferRequest, defaultRail, d
|
||||
return nil, merrors.InvalidArgument("submit_transfer: operation_ref is required")
|
||||
}
|
||||
quoteRef := strings.TrimSpace(metadata[metadataQuoteRef])
|
||||
targetChatID := strings.TrimSpace(metadata[metadataTargetChatID])
|
||||
outgoingLeg := normalizeRail(metadata[metadataOutgoingLeg])
|
||||
if outgoingLeg == "" {
|
||||
outgoingLeg = normalizeRail(defaultRail)
|
||||
}
|
||||
if targetChatID == "" {
|
||||
targetChatID = strings.TrimSpace(defaultChatID)
|
||||
}
|
||||
return &model.PaymentGatewayIntent{
|
||||
PaymentRef: paymentRef,
|
||||
PaymentIntentID: paymentIntentID,
|
||||
|
||||
@@ -32,6 +32,7 @@ func (f *fakePaymentsStore) FindByIdempotencyKey(_ context.Context, key string)
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.records == nil {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent missing records.
|
||||
return nil, nil
|
||||
}
|
||||
return f.records[key], nil
|
||||
@@ -41,6 +42,7 @@ func (f *fakePaymentsStore) FindByOperationRef(_ context.Context, key string) (*
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.records == nil {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent missing records.
|
||||
return nil, nil
|
||||
}
|
||||
for _, record := range f.records {
|
||||
@@ -48,6 +50,7 @@ func (f *fakePaymentsStore) FindByOperationRef(_ context.Context, key string) (*
|
||||
return record, nil
|
||||
}
|
||||
}
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent missing records.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -124,6 +127,7 @@ func (f *fakePendingStore) FindByRequestID(_ context.Context, requestID string)
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.records == nil {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent missing records.
|
||||
return nil, nil
|
||||
}
|
||||
return f.records[requestID], nil
|
||||
@@ -137,6 +141,7 @@ func (f *fakePendingStore) FindByMessageID(_ context.Context, messageID string)
|
||||
return record, nil
|
||||
}
|
||||
}
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent missing records.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -197,6 +202,7 @@ func (f *fakeBroker) Publish(_ envelope.Envelope) error {
|
||||
}
|
||||
|
||||
func (f *fakeBroker) Subscribe(event model.NotificationEvent) (<-chan envelope.Envelope, error) {
|
||||
//nolint:nilnil // Test stub does not create a subscription channel.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -237,7 +243,7 @@ func newTestService(_ *testing.T) (*Service, *fakeRepo, *captureProducer) {
|
||||
pending: &fakePendingStore{},
|
||||
}
|
||||
|
||||
sigEnv := tnotifications.TelegramReaction(string(mservice.PaymentGateway), &model.TelegramReactionRequest{
|
||||
sigEnv := tnotifications.TelegramReaction(mservice.PaymentGateway, &model.TelegramReactionRequest{
|
||||
RequestID: "x",
|
||||
ChatID: "1",
|
||||
MessageID: "2",
|
||||
@@ -407,7 +413,7 @@ func TestIntentFromSubmitTransfer_NormalizesOutgoingLeg(t *testing.T) {
|
||||
Metadata: map[string]string{
|
||||
metadataOutgoingLeg: "card",
|
||||
},
|
||||
}, "provider_settlement", "")
|
||||
}, "provider_settlement")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (s *Service) updateTransferStatus(ctx context.Context, record *model.Paymen
|
||||
return nil, emitErr
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
return struct{}{}, nil
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to update transfer status", zap.String("payment_ref", record.PaymentIntentID), zap.String("status", string(record.Status)), zap.Error(err))
|
||||
|
||||
@@ -22,7 +22,7 @@ const amountInputHint = "*Amount format*\nEnter amount as a decimal number using
|
||||
type SendTextFunc func(ctx context.Context, chatID string, text string) error
|
||||
|
||||
type ScheduleTracker interface {
|
||||
TrackScheduled(record *storagemodel.TreasuryRequest)
|
||||
TrackScheduled(ctx context.Context, record *storagemodel.TreasuryRequest)
|
||||
Untrack(requestID string)
|
||||
}
|
||||
|
||||
@@ -128,6 +128,11 @@ func (r *Router) HandleUpdate(ctx context.Context, update *model.TelegramWebhook
|
||||
|
||||
binding, err := r.users.ResolveUserBinding(ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
r.logUnauthorized(update)
|
||||
_ = r.sendText(ctx, chatID, unauthorizedMessage)
|
||||
return true
|
||||
}
|
||||
if r.logger != nil {
|
||||
r.logger.Warn("Failed to resolve treasury user binding",
|
||||
zap.Error(err),
|
||||
@@ -224,7 +229,7 @@ func (r *Router) HandleUpdate(ctx context.Context, update *model.TelegramWebhook
|
||||
|
||||
func (r *Router) startAmountDialog(ctx context.Context, userID, accountID, chatID string, operation storagemodel.TreasuryOperationType) {
|
||||
active, err := r.service.GetActiveRequestForAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||
if r.logger != nil {
|
||||
r.logger.Warn("Failed to check active treasury request", zap.Error(err), zap.String("telegram_user_id", userID), zap.String("ledger_account_id", accountID))
|
||||
}
|
||||
@@ -267,7 +272,8 @@ func (r *Router) captureAmount(ctx context.Context, userID, accountID, chatID st
|
||||
})
|
||||
return
|
||||
}
|
||||
if typed, ok := err.(limitError); ok {
|
||||
var typed limitError
|
||||
if errors.As(err, &typed) {
|
||||
switch typed.LimitKind() {
|
||||
case "per_operation":
|
||||
_ = r.sendText(ctx, chatID, "*Amount exceeds allowed limit*\n\n*Max per operation:* "+markdownCode(typed.LimitMax())+"\n\nEnter another amount or "+markdownCommand(CommandCancel)+".")
|
||||
@@ -316,7 +322,7 @@ func (r *Router) confirm(ctx context.Context, userID string, accountID string, c
|
||||
return
|
||||
}
|
||||
if r.tracker != nil {
|
||||
r.tracker.TrackScheduled(record)
|
||||
r.tracker.TrackScheduled(ctx, record)
|
||||
}
|
||||
r.dialogs.Clear(userID)
|
||||
delay := int64(r.service.ExecutionDelay().Seconds())
|
||||
@@ -487,7 +493,7 @@ func (r *Router) resolveAccountProfile(ctx context.Context, ledgerAccountID stri
|
||||
}
|
||||
profile, err := r.service.GetAccountProfile(ctx, ledgerAccountID)
|
||||
if err != nil {
|
||||
if r.logger != nil {
|
||||
if r.logger != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||
r.logger.Warn("Failed to resolve treasury account profile",
|
||||
zap.Error(err),
|
||||
zap.String("ledger_account_id", strings.TrimSpace(ledgerAccountID)))
|
||||
|
||||
@@ -22,6 +22,7 @@ func (f fakeUserBindingResolver) ResolveUserBinding(_ context.Context, telegramU
|
||||
return nil, f.err
|
||||
}
|
||||
if f.bindings == nil {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent missing binding.
|
||||
return nil, nil
|
||||
}
|
||||
return f.bindings[telegramUserID], nil
|
||||
@@ -36,6 +37,7 @@ func (fakeService) MaxPerOperationLimit() string {
|
||||
}
|
||||
|
||||
func (fakeService) GetActiveRequestForAccount(context.Context, string) (*storagemodel.TreasuryRequest, error) {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent no active request.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -48,14 +50,17 @@ func (fakeService) GetAccountProfile(_ context.Context, ledgerAccountID string)
|
||||
}
|
||||
|
||||
func (fakeService) CreateRequest(context.Context, CreateRequestInput) (*storagemodel.TreasuryRequest, error) {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent no created request.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (fakeService) ConfirmRequest(context.Context, string, string) (*storagemodel.TreasuryRequest, error) {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent no confirmed request.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (fakeService) CancelRequest(context.Context, string, string) (*storagemodel.TreasuryRequest, error) {
|
||||
//nolint:nilnil // Test stub uses nil, nil to represent no cancelled request.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ func (c *connectorClient) callContext(ctx context.Context) (context.Context, con
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithTimeout(ctx, c.cfg.Timeout)
|
||||
return context.WithTimeout(ctx, c.cfg.Timeout) //nolint:gosec // cancel func is always invoked by call sites
|
||||
}
|
||||
|
||||
func structFromMap(values map[string]any) *structpb.Struct {
|
||||
|
||||
@@ -141,7 +141,7 @@ func (c *discoveryClient) resolveClient(_ context.Context) (Client, error) {
|
||||
c.endpointKey = key
|
||||
if c.logger != nil {
|
||||
c.logger.Info("Discovered ledger endpoint selected",
|
||||
zap.String("service", string(mservice.Ledger)),
|
||||
zap.String("service", mservice.Ledger),
|
||||
zap.String("invoke_uri", endpoint.raw),
|
||||
zap.String("address", endpoint.address),
|
||||
zap.Bool("insecure", endpoint.insecure))
|
||||
@@ -186,10 +186,10 @@ func (c *discoveryClient) resolveEndpoint() (discoveryEndpoint, error) {
|
||||
|
||||
func matchesService(service string, candidate mservice.Type) bool {
|
||||
service = strings.TrimSpace(service)
|
||||
if service == "" || strings.TrimSpace(string(candidate)) == "" {
|
||||
if service == "" || strings.TrimSpace(candidate) == "" {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(service, strings.TrimSpace(string(candidate)))
|
||||
return strings.EqualFold(service, strings.TrimSpace(candidate))
|
||||
}
|
||||
|
||||
func parseDiscoveryEndpoint(raw string) (discoveryEndpoint, error) {
|
||||
|
||||
@@ -106,7 +106,7 @@ func (a *botUsersAdapter) ResolveUserBinding(ctx context.Context, telegramUserID
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury user binding not found")
|
||||
}
|
||||
return &bot.UserBinding{
|
||||
TelegramUserID: strings.TrimSpace(record.TelegramUserID),
|
||||
@@ -145,7 +145,7 @@ func (a *botServiceAdapter) GetAccountProfile(ctx context.Context, ledgerAccount
|
||||
return nil, err
|
||||
}
|
||||
if profile == nil {
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury account profile not found")
|
||||
}
|
||||
return &bot.AccountProfile{
|
||||
AccountID: strings.TrimSpace(profile.AccountID),
|
||||
|
||||
@@ -2,11 +2,13 @@ package treasury
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -86,7 +88,7 @@ func (s *Scheduler) Shutdown() {
|
||||
s.timersMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Scheduler) TrackScheduled(record *storagemodel.TreasuryRequest) {
|
||||
func (s *Scheduler) TrackScheduled(ctx context.Context, record *storagemodel.TreasuryRequest) {
|
||||
if s == nil || s.service == nil || record == nil {
|
||||
return
|
||||
}
|
||||
@@ -101,10 +103,14 @@ func (s *Scheduler) TrackScheduled(record *storagemodel.TreasuryRequest) {
|
||||
if when.IsZero() {
|
||||
when = time.Now()
|
||||
}
|
||||
runCtx := ctx
|
||||
if runCtx == nil {
|
||||
runCtx = context.Background()
|
||||
}
|
||||
delay := time.Until(when)
|
||||
if delay <= 0 {
|
||||
s.Untrack(requestID)
|
||||
go s.executeAndNotifyByID(context.Background(), requestID)
|
||||
go s.executeAndNotifyByID(runCtx, requestID) //nolint:gosec // scheduler intentionally dispatches due execution asynchronously
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +120,7 @@ func (s *Scheduler) TrackScheduled(record *storagemodel.TreasuryRequest) {
|
||||
}
|
||||
s.timers[requestID] = time.AfterFunc(delay, func() {
|
||||
s.Untrack(requestID)
|
||||
s.executeAndNotifyByID(context.Background(), requestID)
|
||||
s.executeAndNotifyByID(runCtx, requestID)
|
||||
})
|
||||
s.timersMu.Unlock()
|
||||
}
|
||||
@@ -145,7 +151,7 @@ func (s *Scheduler) hydrateTimers(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
for _, record := range scheduled {
|
||||
s.TrackScheduled(&record)
|
||||
s.TrackScheduled(ctx, &record)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,16 +193,20 @@ func (s *Scheduler) executeAndNotifyByID(ctx context.Context, requestID string)
|
||||
if requestID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
runCtx := ctx
|
||||
if runCtx == nil {
|
||||
runCtx = context.Background()
|
||||
}
|
||||
|
||||
withTimeout, cancel := context.WithTimeout(runCtx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result, err := s.service.ExecuteRequest(withTimeout, requestID)
|
||||
if err != nil {
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
s.logger.Debug("Treasury request is not due for execution", zap.String("request_id", requestID))
|
||||
return
|
||||
}
|
||||
s.logger.Warn("Failed to execute treasury request", zap.Error(err), zap.String("request_id", requestID))
|
||||
return
|
||||
}
|
||||
@@ -228,11 +238,7 @@ func (s *Scheduler) executeAndNotifyByID(ctx context.Context, requestID string)
|
||||
zap.String("chat_id", chatID),
|
||||
zap.String("status", strings.TrimSpace(string(result.Request.Status))))
|
||||
|
||||
notifyCtx := context.Background()
|
||||
if ctx != nil {
|
||||
notifyCtx = ctx
|
||||
}
|
||||
notifyCtx, notifyCancel := context.WithTimeout(notifyCtx, 15*time.Second)
|
||||
notifyCtx, notifyCancel := context.WithTimeout(runCtx, 15*time.Second)
|
||||
defer notifyCancel()
|
||||
|
||||
if err := s.notify(notifyCtx, chatID, text); err != nil {
|
||||
|
||||
@@ -298,33 +298,33 @@ func (s *Service) ExecuteRequest(ctx context.Context, requestID string) (*Execut
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury request not found")
|
||||
}
|
||||
|
||||
switch record.Status {
|
||||
case storagemodel.TreasuryRequestStatusExecuted,
|
||||
storagemodel.TreasuryRequestStatusCancelled,
|
||||
storagemodel.TreasuryRequestStatusFailed:
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury request is not executable")
|
||||
case storagemodel.TreasuryRequestStatusScheduled:
|
||||
claimed, err := s.repo.ClaimScheduled(ctx, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !claimed {
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury request is already claimed")
|
||||
}
|
||||
record, err = s.repo.FindByRequestID(ctx, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury request not found")
|
||||
}
|
||||
}
|
||||
|
||||
if record.Status != storagemodel.TreasuryRequestStatusConfirmed {
|
||||
return nil, nil
|
||||
return nil, merrors.NoData("treasury request is not confirmed")
|
||||
}
|
||||
return s.executeClaimed(ctx, record)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ func (p *Payments) FindByIdempotencyKey(ctx context.Context, key string) (*model
|
||||
var result model.PaymentRecord
|
||||
err := p.repo.FindOneByFilter(ctx, repository.Filter(fieldIdempotencyKey, key), &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -87,6 +88,7 @@ func (p *Payments) FindByOperationRef(ctx context.Context, key string) (*model.P
|
||||
var result model.PaymentRecord
|
||||
err := p.repo.FindOneByFilter(ctx, repository.Filter(fieldOperationRef, key), &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -114,6 +114,7 @@ func (p *PendingConfirmations) FindByRequestID(ctx context.Context, requestID st
|
||||
var result model.PendingConfirmation
|
||||
err := p.repo.FindOneByFilter(ctx, repository.Filter(fieldPendingRequestID, requestID), &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -130,6 +131,7 @@ func (p *PendingConfirmations) FindByMessageID(ctx context.Context, messageID st
|
||||
var result model.PendingConfirmation
|
||||
err := p.repo.FindOneByFilter(ctx, repository.Filter(fieldPendingMessageID, messageID), &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -165,6 +165,7 @@ func (t *TreasuryRequests) FindByRequestID(ctx context.Context, requestID string
|
||||
err := t.repo.FindOneByFilter(ctx, repository.Filter(fieldTreasuryRequestID, requestID), &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
t.logger.Debug("Treasury request not found", zap.String("request_id", requestID))
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -190,6 +191,7 @@ func (t *TreasuryRequests) FindActiveByLedgerAccountID(ctx context.Context, ledg
|
||||
err := t.repo.FindOneByFilter(ctx, query, &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
t.logger.Debug("Active treasury request not found", zap.String("ledger_account_id", ledgerAccountID))
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -57,6 +57,7 @@ func (t *TreasuryTelegramUsers) FindByTelegramUserID(ctx context.Context, telegr
|
||||
var result model.TreasuryTelegramUser
|
||||
err := t.repo.FindOneByFilter(ctx, repository.Filter(fieldTreasuryTelegramUserID, telegramUserID), &result)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
//nolint:nilnil // Not-found is represented as (nil, nil) by this store contract.
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -79,6 +80,7 @@ func (t *TreasuryTelegramUsers) FindByTelegramUserID(ctx context.Context, telegr
|
||||
result.AllowedChatIDs = normalized
|
||||
}
|
||||
if result.TelegramUserID == "" || result.LedgerAccountID == "" {
|
||||
//nolint:nilnil // Invalid/incomplete records are treated as missing binding.
|
||||
return nil, nil
|
||||
}
|
||||
return &result, nil
|
||||
|
||||
Reference in New Issue
Block a user