new settlement flow
This commit is contained in:
@@ -9,9 +9,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/notification/internal/server/notificationimp/telegram"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
msg "github.com/tech/sendico/pkg/messaging"
|
||||
confirmations "github.com/tech/sendico/pkg/messaging/notifications/confirmations"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -35,12 +35,12 @@ type confirmationManager struct {
|
||||
}
|
||||
|
||||
type confirmationState struct {
|
||||
request model.ConfirmationRequest
|
||||
requestMessageID string
|
||||
targetChatID string
|
||||
callbackSubject string
|
||||
clarified bool
|
||||
timer *time.Timer
|
||||
request model.ConfirmationRequest
|
||||
requestMessageID string
|
||||
targetChatID string
|
||||
callbackSubject string
|
||||
clarified bool
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
func newConfirmationManager(logger mlogger.Logger, tg telegram.Client, outbox msg.Producer) *confirmationManager {
|
||||
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
"github.com/tech/sendico/pkg/domainprovider"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
na "github.com/tech/sendico/pkg/messaging/notifications/account"
|
||||
confirmations "github.com/tech/sendico/pkg/messaging/notifications/confirmations"
|
||||
cnotifications "github.com/tech/sendico/pkg/messaging/notifications/confirmation"
|
||||
confirmations "github.com/tech/sendico/pkg/messaging/notifications/confirmations"
|
||||
ni "github.com/tech/sendico/pkg/messaging/notifications/invitation"
|
||||
snotifications "github.com/tech/sendico/pkg/messaging/notifications/site"
|
||||
tnotifications "github.com/tech/sendico/pkg/messaging/notifications/telegram"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -91,6 +92,10 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
|
||||
p.logger.Error("Failed to register confirmation request handler", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if err := a.Register().Consumer(tnotifications.NewTelegramReactionProcessor(p.logger, p.onTelegramReaction)); err != nil {
|
||||
p.logger.Error("Failed to register telegram reaction handler", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idb, err := a.DBFactory().NewInvitationsDB()
|
||||
if err != nil {
|
||||
|
||||
@@ -26,6 +26,7 @@ type Client interface {
|
||||
SendContactRequest(ctx context.Context, request *model.ContactRequest) error
|
||||
SendCallRequest(ctx context.Context, request *model.CallRequest) error
|
||||
SendText(ctx context.Context, chatID string, text string, replyToMessageID string) (*model.TelegramMessage, error)
|
||||
SetMessageReaction(ctx context.Context, chatID string, messageID string, emoji string) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
@@ -132,6 +133,25 @@ type messageUser struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
type setMessageReactionPayload struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
MessageID int64 `json:"message_id"`
|
||||
Reaction []reactionType `json:"reaction,omitempty"`
|
||||
IsBig bool `json:"is_big,omitempty"`
|
||||
}
|
||||
|
||||
type reactionType struct {
|
||||
Type string `json:"type"`
|
||||
Emoji string `json:"emoji,omitempty"`
|
||||
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
|
||||
}
|
||||
|
||||
type setMessageReactionResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
Result bool `json:"result,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) (*model.TelegramMessage, error) {
|
||||
body, err := json.Marshal(&payload)
|
||||
if err != nil {
|
||||
@@ -193,7 +213,12 @@ func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) (*
|
||||
}
|
||||
|
||||
func (c *client) endpoint() string {
|
||||
return fmt.Sprintf("%s/bot%s/sendMessage", c.apiURL, c.botToken)
|
||||
return c.endpointFor("sendMessage")
|
||||
}
|
||||
|
||||
func (c *client) endpointFor(method string) string {
|
||||
method = strings.TrimPrefix(strings.TrimSpace(method), "/")
|
||||
return fmt.Sprintf("%s/bot%s/%s", c.apiURL, c.botToken, method)
|
||||
}
|
||||
|
||||
func (c *client) SendContactRequest(ctx context.Context, request *model.ContactRequest) error {
|
||||
@@ -248,6 +273,80 @@ func (c *client) SendText(ctx context.Context, chatID string, text string, reply
|
||||
return c.sendMessage(ctx, payload)
|
||||
}
|
||||
|
||||
func (c *client) SetMessageReaction(ctx context.Context, chatID string, messageID string, emoji string) error {
|
||||
chatID = strings.TrimSpace(chatID)
|
||||
if chatID == "" {
|
||||
return merrors.InvalidArgument("telegram chat id is empty", "chat_id")
|
||||
}
|
||||
msgID, err := strconv.ParseInt(strings.TrimSpace(messageID), 10, 64)
|
||||
if err != nil {
|
||||
return merrors.InvalidArgumentWrap(err, "invalid message_id", "message_id")
|
||||
}
|
||||
emoji = strings.TrimSpace(emoji)
|
||||
reaction := []reactionType{}
|
||||
if emoji != "" {
|
||||
reaction = append(reaction, reactionType{Type: "emoji", Emoji: emoji})
|
||||
}
|
||||
payload := setMessageReactionPayload{
|
||||
ChatID: chatID,
|
||||
MessageID: msgID,
|
||||
Reaction: reaction,
|
||||
}
|
||||
return c.sendReaction(ctx, payload)
|
||||
}
|
||||
|
||||
func (c *client) sendReaction(ctx context.Context, payload setMessageReactionPayload) error {
|
||||
body, err := json.Marshal(&payload)
|
||||
if err != nil {
|
||||
c.logger.Warn("Failed to marshal telegram reaction payload", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpointFor("setMessageReaction"), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
c.logger.Warn("Failed to create telegram reaction request", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
fields := []zap.Field{
|
||||
zap.String("chat_id", payload.ChatID),
|
||||
zap.Int64("message_id", payload.MessageID),
|
||||
zap.Int("payload_size_bytes", len(body)),
|
||||
}
|
||||
c.logger.Debug("Sending Telegram reaction", fields...)
|
||||
start := time.Now()
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Warn("Telegram reaction request failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 16<<10))
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
var parsed setMessageReactionResponse
|
||||
if err := json.Unmarshal(respBody, &parsed); err != nil {
|
||||
c.logger.Warn("Failed to decode telegram reaction response", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if !parsed.OK || !parsed.Result {
|
||||
msg := "telegram setMessageReaction response missing result"
|
||||
if parsed.Description != "" {
|
||||
msg = parsed.Description
|
||||
}
|
||||
return merrors.Internal(msg)
|
||||
}
|
||||
c.logger.Debug("Telegram reaction sent", zap.Int("status_code", resp.StatusCode), zap.Duration("latency", time.Since(start)))
|
||||
return nil
|
||||
}
|
||||
c.logger.Warn("Telegram API returned non-success status for reaction",
|
||||
zap.Int("status_code", resp.StatusCode),
|
||||
zap.ByteString("response_body", respBody),
|
||||
zap.String("chat_id", payload.ChatID))
|
||||
return merrors.Internal(fmt.Sprintf("telegram setMessageReaction failed with status %d: %s", resp.StatusCode, string(respBody)))
|
||||
}
|
||||
|
||||
func toTelegramMessage(msg *messageResponse) *model.TelegramMessage {
|
||||
if msg == nil {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package notificationimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *NotificationAPI) onTelegramReaction(ctx context.Context, request *model.TelegramReactionRequest) error {
|
||||
if request == nil {
|
||||
return merrors.InvalidArgument("telegram reaction request is nil", "request")
|
||||
}
|
||||
if a.tg == nil {
|
||||
return merrors.Internal("telegram client is not configured")
|
||||
}
|
||||
chatID := strings.TrimSpace(request.ChatID)
|
||||
messageID := strings.TrimSpace(request.MessageID)
|
||||
if chatID == "" {
|
||||
return merrors.InvalidArgument("telegram chat_id is required", "chat_id")
|
||||
}
|
||||
if messageID == "" {
|
||||
return merrors.InvalidArgument("telegram message_id is required", "message_id")
|
||||
}
|
||||
emoji := strings.TrimSpace(request.Emoji)
|
||||
if emoji == "" {
|
||||
return merrors.InvalidArgument("telegram emoji is required", "emoji")
|
||||
}
|
||||
if err := a.tg.SetMessageReaction(ctx, chatID, messageID, emoji); err != nil {
|
||||
a.logger.Warn("Failed to send telegram reaction",
|
||||
zap.Error(err),
|
||||
zap.String("request_id", strings.TrimSpace(request.RequestID)),
|
||||
zap.String("chat_id", chatID),
|
||||
zap.String("message_id", messageID),
|
||||
zap.String("emoji", emoji))
|
||||
return err
|
||||
}
|
||||
a.logger.Info("Telegram reaction sent",
|
||||
zap.String("request_id", strings.TrimSpace(request.RequestID)),
|
||||
zap.String("chat_id", chatID),
|
||||
zap.String("message_id", messageID),
|
||||
zap.String("emoji", emoji))
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user