From 2bf05ab414d0bbd1a16d427a5c0fa6ff4fccf487 Mon Sep 17 00:00:00 2001 From: Stephan D Date: Thu, 19 Feb 2026 21:08:24 +0100 Subject: [PATCH] extended confirmation flow handling --- .../service/gateway/confirmation_flow.go | 73 +++++++++++++++++-- api/payments/storage/model/plan_template.go | 18 ++--- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/api/gateway/tgsettle/internal/service/gateway/confirmation_flow.go b/api/gateway/tgsettle/internal/service/gateway/confirmation_flow.go index 384994a5..5abc0eed 100644 --- a/api/gateway/tgsettle/internal/service/gateway/confirmation_flow.go +++ b/api/gateway/tgsettle/internal/service/gateway/confirmation_flow.go @@ -148,14 +148,23 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe if replyToID == "" { return nil } + replyFields := telegramReplyLogFields(update) pending, err := s.repo.PendingConfirmations().FindByMessageID(ctx, replyToID) if err != nil { return err } if pending == nil { - s.logger.Debug("Telegram reply ignored: no pending confirmation for message", zap.String("reply_to_message_id", replyToID), zap.Int64("update_id", update.UpdateID)) + s.logger.Info("Telegram confirmation reply dropped", + append(replyFields, + zap.String("outcome", "dropped"), + zap.String("reason", "no_pending_confirmation"), + )...) return nil } + replyFields = append(replyFields, + zap.String("request_id", strings.TrimSpace(pending.RequestID)), + zap.String("target_chat_id", strings.TrimSpace(pending.TargetChatID)), + ) if !pending.ExpiresAt.IsZero() && time.Now().After(pending.ExpiresAt) { result := &model.ConfirmationResult{ @@ -165,14 +174,25 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe if err := s.publishPendingConfirmationResult(pending, result); err != nil { return err } - return s.clearPendingConfirmation(ctx, pending.RequestID) + if err := s.clearPendingConfirmation(ctx, pending.RequestID); err != nil { + return err + } + s.logger.Info("Telegram confirmation reply processed", + append(replyFields, + zap.String("outcome", "processed"), + zap.String("result_status", string(result.Status)), + zap.String("reason", "expired_confirmation"), + )...) + return nil } if strings.TrimSpace(message.ChatID) != strings.TrimSpace(pending.TargetChatID) { - s.logger.Debug("Telegram reply ignored: chat mismatch", - zap.String("request_id", pending.RequestID), - zap.String("expected_chat_id", pending.TargetChatID), - zap.String("chat_id", strings.TrimSpace(message.ChatID))) + s.logger.Info("Telegram confirmation reply dropped", + append(replyFields, + zap.String("outcome", "dropped"), + zap.String("reason", "chat_mismatch"), + zap.String("expected_chat_id", strings.TrimSpace(pending.TargetChatID)), + )...) return nil } @@ -192,7 +212,16 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe ReplyToMessageID: message.MessageID, Text: "Only approved users can confirm this payment.", }) - return s.clearPendingConfirmation(ctx, pending.RequestID) + if err := s.clearPendingConfirmation(ctx, pending.RequestID); err != nil { + return err + } + s.logger.Info("Telegram confirmation reply processed", + append(replyFields, + zap.String("outcome", "processed"), + zap.String("result_status", string(result.Status)), + zap.String("reason", "unauthorized_user"), + )...) + return nil } money, reason, err := parseConfirmationReply(message.Text) @@ -206,6 +235,12 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe ReplyToMessageID: message.MessageID, Text: clarificationMessage(reason), }) + s.logger.Info("Telegram confirmation reply dropped", + append(replyFields, + zap.String("outcome", "dropped"), + zap.String("reason", "invalid_reply_format"), + zap.String("parse_reason", reason), + )...) return nil } @@ -222,7 +257,29 @@ func (s *Service) onTelegramUpdate(ctx context.Context, update *model.TelegramWe if err := s.publishPendingConfirmationResult(pending, result); err != nil { return err } - return s.clearPendingConfirmation(ctx, pending.RequestID) + if err := s.clearPendingConfirmation(ctx, pending.RequestID); err != nil { + return err + } + s.logger.Info("Telegram confirmation reply processed", + append(replyFields, + zap.String("outcome", "processed"), + zap.String("result_status", string(result.Status)), + )...) + return nil +} + +func telegramReplyLogFields(update *model.TelegramWebhookUpdate) []zap.Field { + if update == nil || update.Message == nil { + return nil + } + message := update.Message + return []zap.Field{ + zap.Int64("update_id", update.UpdateID), + zap.String("message_id", strings.TrimSpace(message.MessageID)), + zap.String("reply_to_message_id", strings.TrimSpace(message.ReplyToMessageID)), + zap.String("chat_id", strings.TrimSpace(message.ChatID)), + zap.String("from_user_id", strings.TrimSpace(message.FromUserID)), + } } func (s *Service) publishPendingConfirmationResult(pending *storagemodel.PendingConfirmation, result *model.ConfirmationResult) error { diff --git a/api/payments/storage/model/plan_template.go b/api/payments/storage/model/plan_template.go index 74329cc4..b33d7c35 100644 --- a/api/payments/storage/model/plan_template.go +++ b/api/payments/storage/model/plan_template.go @@ -10,15 +10,15 @@ import ( // OrchestrationStep defines a template step for execution planning. type OrchestrationStep struct { - StepID string `bson:"stepId" json:"stepId"` - Rail Rail `bson:"rail" json:"rail"` - Operation string `bson:"operation" json:"operation"` - ReportVisibility ReportVisibility `bson:"reportVisibility,omitempty" json:"reportVisibility,omitempty"` - DependsOn []string `bson:"dependsOn,omitempty" json:"dependsOn,omitempty"` - CommitPolicy CommitPolicy `bson:"commitPolicy,omitempty" json:"commitPolicy,omitempty"` - CommitAfter []string `bson:"commitAfter,omitempty" json:"commitAfter,omitempty"` - FromRole *account_role.AccountRole `bson:"fromRole,omitempty" json:"fromRole,omitempty"` - ToRole *account_role.AccountRole `bson:"toRole,omitempty" json:"toRole,omitempty"` + StepID string `bson:"stepId" json:"stepId"` + Rail Rail `bson:"rail" json:"rail"` + Operation string `bson:"operation" json:"operation"` + ReportVisibility ReportVisibility `bson:"reportVisibility,omitempty" json:"reportVisibility,omitempty"` + DependsOn []string `bson:"dependsOn,omitempty" json:"dependsOn,omitempty"` + CommitPolicy CommitPolicy `bson:"commitPolicy,omitempty" json:"commitPolicy,omitempty"` + CommitAfter []string `bson:"commitAfter,omitempty" json:"commitAfter,omitempty"` + FromRole *account_role.AccountRole `bson:"fromRole,omitempty" json:"fromRole,omitempty"` + ToRole *account_role.AccountRole `bson:"toRole,omitempty" json:"toRole,omitempty"` } // PaymentPlanTemplate stores reusable orchestration templates.