new settlement flow

This commit is contained in:
Stephan D
2026-01-20 22:29:30 +01:00
parent ae6c617136
commit e0d7320fad
42 changed files with 428 additions and 73 deletions

View File

@@ -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