fixed fee direction

This commit is contained in:
Stephan D
2026-03-05 13:24:41 +01:00
parent 1e376da719
commit 4a5e26b03a
69 changed files with 8677 additions and 82 deletions

View File

@@ -14,10 +14,10 @@ import (
"go.uber.org/zap"
)
const unauthorizedMessage = "Sorry, your Telegram account is not authorized to perform treasury operations."
const unauthorizedChatMessage = "Sorry, this Telegram chat is not authorized to perform treasury operations."
const unauthorizedMessage = "*Unauthorized*\nYour Telegram account is not allowed to perform treasury operations."
const unauthorizedChatMessage = "*Unauthorized Chat*\nThis Telegram chat is not allowed to perform treasury operations."
const amountInputHint = "Enter amount as a decimal number using a dot separator and without currency.\nExample: 1250.75"
const amountInputHint = "*Amount format*\nEnter amount as a decimal number using a dot separator and without currency.\nExample: `1250.75`"
type SendTextFunc func(ctx context.Context, chatID string, text string) error
@@ -232,7 +232,7 @@ func (r *Router) startAmountDialog(ctx context.Context, userID, accountID, chatI
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))
}
_ = r.sendText(ctx, chatID, "Unable to check pending treasury operations right now. Please try again.")
_ = r.sendText(ctx, chatID, "*Temporary issue*\nUnable to check pending treasury operations right now. Please try again.")
return
}
if active != nil {
@@ -274,22 +274,22 @@ func (r *Router) captureAmount(ctx context.Context, userID, accountID, chatID st
if typed, ok := err.(limitError); ok {
switch typed.LimitKind() {
case "per_operation":
_ = r.sendText(ctx, chatID, "Amount exceeds allowed limit.\n\nMax per operation: "+typed.LimitMax()+"\n\nEnter another amount or "+CommandCancel.Slash())
_ = r.sendText(ctx, chatID, "*Amount exceeds allowed limit*\n\n*Max per operation:* "+markdownCode(typed.LimitMax())+"\n\nEnter another amount or "+markdownCommand(CommandCancel)+".")
return
case "daily":
_ = r.sendText(ctx, chatID, "Daily amount limit exceeded.\n\nMax per day: "+typed.LimitMax()+"\n\nEnter another amount or "+CommandCancel.Slash())
_ = r.sendText(ctx, chatID, "*Daily amount limit exceeded*\n\n*Max per day:* "+markdownCode(typed.LimitMax())+"\n\nEnter another amount or "+markdownCommand(CommandCancel)+".")
return
}
}
if errors.Is(err, merrors.ErrInvalidArg) {
_ = r.sendText(ctx, chatID, "Invalid amount.\n\n"+amountInputHint+"\n\nEnter another amount or "+CommandCancel.Slash())
_ = r.sendText(ctx, chatID, "*Invalid amount*\n\n"+amountInputHint+"\n\nEnter another amount or "+markdownCommand(CommandCancel)+".")
return
}
_ = r.sendText(ctx, chatID, "Failed to create treasury request.\n\nEnter another amount or "+CommandCancel.Slash())
_ = r.sendText(ctx, chatID, "*Failed to create treasury request*\n\nEnter another amount or "+markdownCommand(CommandCancel)+".")
return
}
if record == nil {
_ = r.sendText(ctx, chatID, "Failed to create treasury request.\n\nEnter another amount or "+CommandCancel.Slash())
_ = r.sendText(ctx, chatID, "*Failed to create treasury request*\n\nEnter another amount or "+markdownCommand(CommandCancel)+".")
return
}
r.dialogs.Set(userID, DialogSession{
@@ -311,12 +311,12 @@ func (r *Router) confirm(ctx context.Context, userID string, accountID string, c
}
}
if requestID == "" {
_ = r.sendText(ctx, chatID, "No pending treasury operation.")
_ = r.sendText(ctx, chatID, "*No pending treasury operation.*")
return
}
record, err := r.service.ConfirmRequest(ctx, requestID, userID)
if err != nil {
_ = r.sendText(ctx, chatID, "Unable to confirm treasury request.\n\nUse "+CommandCancel.Slash()+" or create a new request with "+CommandFund.Slash()+" or "+CommandWithdraw.Slash()+".")
_ = r.sendText(ctx, chatID, "*Unable to confirm treasury request.*\n\nUse "+markdownCommand(CommandCancel)+" or create a new request with "+markdownCommand(CommandFund)+" or "+markdownCommand(CommandWithdraw)+".")
return
}
if r.tracker != nil {
@@ -327,7 +327,12 @@ func (r *Router) confirm(ctx context.Context, userID string, accountID string, c
if delay < 0 {
delay = 0
}
_ = r.sendText(ctx, chatID, "Operation confirmed.\n\nExecution scheduled in "+formatSeconds(delay)+".\nYou can cancel during this cooldown with "+CommandCancel.Slash()+".\n\nYou will receive a follow-up message with execution success or failure.\n\nRequest ID: "+strings.TrimSpace(record.RequestID))
_ = r.sendText(ctx, chatID,
"*Operation confirmed*\n\n"+
"*Execution:* scheduled in "+markdownCode(formatSeconds(delay))+".\n"+
"You can cancel during this cooldown with "+markdownCommand(CommandCancel)+".\n\n"+
"You will receive a follow-up message with execution success or failure.\n\n"+
"*Request ID:* "+markdownCode(strings.TrimSpace(record.RequestID)))
}
func (r *Router) cancel(ctx context.Context, userID string, accountID string, chatID string) {
@@ -342,19 +347,19 @@ func (r *Router) cancel(ctx context.Context, userID string, accountID string, ch
}
if requestID == "" {
r.dialogs.Clear(userID)
_ = r.sendText(ctx, chatID, "No pending treasury operation.")
_ = r.sendText(ctx, chatID, "*No pending treasury operation.*")
return
}
record, err := r.service.CancelRequest(ctx, requestID, userID)
if err != nil {
_ = r.sendText(ctx, chatID, "Unable to cancel treasury request.")
_ = r.sendText(ctx, chatID, "*Unable to cancel treasury request.*")
return
}
if r.tracker != nil {
r.tracker.Untrack(record.RequestID)
}
r.dialogs.Clear(userID)
_ = r.sendText(ctx, chatID, "Operation cancelled.\n\nRequest ID: "+strings.TrimSpace(record.RequestID))
_ = r.sendText(ctx, chatID, "*Operation cancelled*\n\n*Request ID:* "+markdownCode(strings.TrimSpace(record.RequestID)))
}
func (r *Router) sendText(ctx context.Context, chatID string, text string) error {
@@ -394,27 +399,27 @@ func (r *Router) logUnauthorized(update *model.TelegramWebhookUpdate) {
func pendingRequestMessage(record *storagemodel.TreasuryRequest) string {
if record == nil {
return "You already have a pending treasury operation.\n\n" + CommandCancel.Slash()
return "*Pending treasury operation already exists.*\n\nUse " + markdownCommand(CommandCancel) + "."
}
return "You already have a pending treasury operation.\n\n" +
"Account: " + requestAccountDisplay(record) + "\n" +
"Request ID: " + strings.TrimSpace(record.RequestID) + "\n" +
"Status: " + strings.TrimSpace(string(record.Status)) + "\n" +
"Amount: " + strings.TrimSpace(record.Amount) + " " + strings.TrimSpace(record.Currency) + "\n\n" +
"Wait for execution or cancel it.\n\n" + CommandCancel.Slash()
return "*Pending Treasury Operation*\n\n" +
"*Account:* " + markdownCode(requestAccountDisplay(record)) + "\n" +
"*Request ID:* " + markdownCode(strings.TrimSpace(record.RequestID)) + "\n" +
"*Status:* " + markdownCode(strings.TrimSpace(string(record.Status))) + "\n" +
"*Amount:* " + markdownCode(strings.TrimSpace(record.Amount)+" "+strings.TrimSpace(record.Currency)) + "\n\n" +
"Wait for execution or cancel with " + markdownCommand(CommandCancel) + "."
}
func confirmationPrompt(record *storagemodel.TreasuryRequest) string {
if record == nil {
return "Request created.\n\n" + CommandConfirm.Slash() + "\n" + CommandCancel.Slash()
return "*Request created.*\n\nUse " + markdownCommand(CommandConfirm) + " or " + markdownCommand(CommandCancel) + "."
}
title := "Funding request created."
title := "*Funding request created.*"
if record.OperationType == storagemodel.TreasuryOperationWithdraw {
title = "Withdrawal request created."
title = "*Withdrawal request created.*"
}
return title + "\n\n" +
"Account: " + requestAccountDisplay(record) + "\n" +
"Amount: " + strings.TrimSpace(record.Amount) + " " + strings.TrimSpace(record.Currency) + "\n\n" +
"*Account:* " + markdownCode(requestAccountDisplay(record)) + "\n" +
"*Amount:* " + markdownCode(strings.TrimSpace(record.Amount)+" "+strings.TrimSpace(record.Currency)) + "\n\n" +
confirmationCommandsMessage()
}
@@ -430,13 +435,17 @@ func welcomeMessage(profile *AccountProfile) string {
if currency == "" {
currency = "N/A"
}
return "Welcome to Sendico treasury bot.\n\nAttached account: " + accountCode + " (" + currency + ").\nUse " + CommandFund.Slash() + " to credit your account and " + CommandWithdraw.Slash() + " to debit it.\nAfter entering an amount, use " + CommandConfirm.Slash() + " or " + CommandCancel.Slash() + ".\nUse " + CommandHelp.Slash() + " for detailed usage."
return "*Sendico Treasury Bot*\n\n" +
"*Attached account:* " + markdownCode(accountCode) + " (" + markdownCode(currency) + ")\n" +
"Use " + markdownCommand(CommandFund) + " to credit your account and " + markdownCommand(CommandWithdraw) + " to debit it.\n" +
"After entering an amount, use " + markdownCommand(CommandConfirm) + " or " + markdownCommand(CommandCancel) + ".\n" +
"Use " + markdownCommand(CommandHelp) + " for detailed usage."
}
func amountPromptMessage(operation storagemodel.TreasuryOperationType, profile *AccountProfile, fallbackAccountID string) string {
action := "fund"
title := "*Funding request*"
if operation == storagemodel.TreasuryOperationWithdraw {
action = "withdraw"
title = "*Withdrawal request*"
}
accountCode := displayAccountCode(profile, fallbackAccountID)
currency := ""
@@ -449,7 +458,9 @@ func amountPromptMessage(operation storagemodel.TreasuryOperationType, profile *
if currency == "" {
currency = "N/A"
}
return "Preparing to " + action + " account " + accountCode + " (" + currency + ").\n\n" + amountInputHint
return title + "\n\n" +
"*Account:* " + markdownCode(accountCode) + " (" + markdownCode(currency) + ")\n\n" +
amountInputHint
}
func requestAccountDisplay(record *storagemodel.TreasuryRequest) string {