From 7e4eac0a468701c4ac86d184f99a0738255107f4 Mon Sep 17 00:00:00 2001 From: xtclovver Date: Mon, 23 Mar 2026 22:59:16 +0300 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D1=82=D1=8B=20=D0=BD=D0=B5=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BB=D0=B6=D0=BD=D1=8B=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=D1=81=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=81?= =?UTF-8?q?=D0=B5=20=D1=83=D1=81=D0=BF=D0=B5=D1=88=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=BB=D0=B0=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/bot/payment.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/bot/payment.go b/internal/bot/payment.go index 6b9ba08..3a6b5d9 100644 --- a/internal/bot/payment.go +++ b/internal/bot/payment.go @@ -11,6 +11,7 @@ import ( "github.com/fus1ond/vpn_bot/internal/callback" "github.com/fus1ond/vpn_bot/internal/database" "github.com/fus1ond/vpn_bot/internal/platega" + tele "gopkg.in/telebot.v3" ) // paymentMu — мьютексы по telegram_id для защиты от race condition при обработке callback. @@ -156,8 +157,20 @@ func (b *Bot) paymentActivatedMessage(telegramID int64) string { } func (h *paymentCallbackHandler) finalizeActivatedPayment(payment *database.Payment, notifyUser bool) { + // Сбрасываем состояние оплаты, чтобы убрать клавиатуру "Проверить оплату" + h.bot.userStates.Delete(payment.TelegramID) + if notifyUser { - _ = h.bot.sendSchedulerMessage(payment.TelegramID, h.bot.paymentActivatedMessage(payment.TelegramID)) + msg := h.bot.paymentActivatedMessage(payment.TelegramID) + kb := h.bot.userKeyboard(payment.TelegramID) + _, err := h.bot.bot.Send(&tele.User{ID: payment.TelegramID}, msg, &tele.SendOptions{ + ParseMode: tele.ModeHTML, + ReplyMarkup: kb, + }) + if err != nil { + slog.Warn("Не удалось отправить уведомление об активации с клавиатурой", + "telegram_id", payment.TelegramID, "error", err) + } } // Очищаем уведомления (пользователь мог быть в grace period) From fa166d79a895198804bbd949da1287979ebcb462 Mon Sep 17 00:00:00 2001 From: xtclovver Date: Mon, 23 Mar 2026 23:49:33 +0300 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0?= =?UTF-8?q?=D1=89=D0=B0=D1=82=D1=8C=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20callback=20=D0=BE=D0=BF=D0=BB=D0=B0=D1=82=D1=8B/=D0=BE?= =?UTF-8?q?=D1=82=D0=BC=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При получении callback от Platega (успешная оплата или отмена) бот отправлял сообщение без клавиатуры — кнопки «Проверить оплату» и «Отмена» оставались на месте. Теперь callback-уведомления отправляются с userKeyboard, а состояние ожидания оплаты сбрасывается. Co-Authored-By: Claude Opus 4.6 --- internal/bot/payment.go | 17 ++++------------- internal/bot/scheduler.go | 12 ++++++++++++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/internal/bot/payment.go b/internal/bot/payment.go index 3a6b5d9..08199ce 100644 --- a/internal/bot/payment.go +++ b/internal/bot/payment.go @@ -11,7 +11,6 @@ import ( "github.com/fus1ond/vpn_bot/internal/callback" "github.com/fus1ond/vpn_bot/internal/database" "github.com/fus1ond/vpn_bot/internal/platega" - tele "gopkg.in/telebot.v3" ) // paymentMu — мьютексы по telegram_id для защиты от race condition при обработке callback. @@ -157,20 +156,11 @@ func (b *Bot) paymentActivatedMessage(telegramID int64) string { } func (h *paymentCallbackHandler) finalizeActivatedPayment(payment *database.Payment, notifyUser bool) { - // Сбрасываем состояние оплаты, чтобы убрать клавиатуру "Проверить оплату" + // Сбрасываем состояние ожидания оплаты h.bot.userStates.Delete(payment.TelegramID) if notifyUser { - msg := h.bot.paymentActivatedMessage(payment.TelegramID) - kb := h.bot.userKeyboard(payment.TelegramID) - _, err := h.bot.bot.Send(&tele.User{ID: payment.TelegramID}, msg, &tele.SendOptions{ - ParseMode: tele.ModeHTML, - ReplyMarkup: kb, - }) - if err != nil { - slog.Warn("Не удалось отправить уведомление об активации с клавиатурой", - "telegram_id", payment.TelegramID, "error", err) - } + _ = h.bot.sendSchedulerMessageWithKeyboard(payment.TelegramID, h.bot.paymentActivatedMessage(payment.TelegramID), h.bot.userKeyboard(payment.TelegramID)) } // Очищаем уведомления (пользователь мог быть в grace period) @@ -409,7 +399,8 @@ func (h *paymentCallbackHandler) handleCanceled(payment *database.Payment) error if err := h.bot.db.UpdatePaymentStatus(payment.ID, "canceled"); err != nil { return fmt.Errorf("update status to canceled: %w", err) } - _ = h.bot.sendSchedulerMessage(payment.TelegramID, "❌ Платёж отменён. Вы можете попробовать снова.") + h.bot.userStates.Delete(payment.TelegramID) + _ = h.bot.sendSchedulerMessageWithKeyboard(payment.TelegramID, "❌ Платёж отменён. Вы можете попробовать снова.", h.bot.userKeyboard(payment.TelegramID)) return nil } diff --git a/internal/bot/scheduler.go b/internal/bot/scheduler.go index 78e048a..f9ab126 100644 --- a/internal/bot/scheduler.go +++ b/internal/bot/scheduler.go @@ -340,6 +340,18 @@ func (b *Bot) sendSchedulerMessage(telegramID int64, message string) error { return err } +// sendSchedulerMessageWithKeyboard отправляет сообщение с клавиатурой (для замены текущих кнопок) +func (b *Bot) sendSchedulerMessageWithKeyboard(telegramID int64, message string, markup *tele.ReplyMarkup) error { + if b.bot == nil { + return fmt.Errorf("telegram bot is not initialized") + } + _, err := b.bot.Send(&tele.User{ID: telegramID}, message, &tele.SendOptions{ + ParseMode: tele.ModeHTML, + ReplyMarkup: markup, + }) + return err +} + // isSchedulerForbiddenError проверяет, заблокировал ли пользователь бот или деактивирован. func isSchedulerForbiddenError(err error) bool { return errors.Is(err, tele.ErrBlockedByUser) || From 775b08c7b54d8deeb11315de367d01ba7ce86af0 Mon Sep 17 00:00:00 2001 From: xtclovver Date: Mon, 23 Mar 2026 23:59:58 +0300 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=D1=81=D0=B1=D1=80=D0=B0=D1=81=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BB=D0=B0=D1=82=D1=91=D0=B6=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Безусловный Delete мог затереть состояние другого flow, если пользователь уже перешёл в него до прихода callback от Platega. Теперь состояние удаляется только если оно StateWaitPaymentMethod или StateWaitPaymentResult. Co-Authored-By: Claude Opus 4.6 --- internal/bot/payment.go | 6 +++--- internal/bot/state_map.go | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/internal/bot/payment.go b/internal/bot/payment.go index 08199ce..42bf441 100644 --- a/internal/bot/payment.go +++ b/internal/bot/payment.go @@ -156,8 +156,8 @@ func (b *Bot) paymentActivatedMessage(telegramID int64) string { } func (h *paymentCallbackHandler) finalizeActivatedPayment(payment *database.Payment, notifyUser bool) { - // Сбрасываем состояние ожидания оплаты - h.bot.userStates.Delete(payment.TelegramID) + // Сбрасываем состояние только если пользователь всё ещё в платёжном flow + h.bot.userStates.DeleteIfOneOf(payment.TelegramID, StateWaitPaymentMethod, StateWaitPaymentResult) if notifyUser { _ = h.bot.sendSchedulerMessageWithKeyboard(payment.TelegramID, h.bot.paymentActivatedMessage(payment.TelegramID), h.bot.userKeyboard(payment.TelegramID)) @@ -399,7 +399,7 @@ func (h *paymentCallbackHandler) handleCanceled(payment *database.Payment) error if err := h.bot.db.UpdatePaymentStatus(payment.ID, "canceled"); err != nil { return fmt.Errorf("update status to canceled: %w", err) } - h.bot.userStates.Delete(payment.TelegramID) + h.bot.userStates.DeleteIfOneOf(payment.TelegramID, StateWaitPaymentMethod, StateWaitPaymentResult) _ = h.bot.sendSchedulerMessageWithKeyboard(payment.TelegramID, "❌ Платёж отменён. Вы можете попробовать снова.", h.bot.userKeyboard(payment.TelegramID)) return nil } diff --git a/internal/bot/state_map.go b/internal/bot/state_map.go index ae184df..3300ac1 100644 --- a/internal/bot/state_map.go +++ b/internal/bot/state_map.go @@ -35,3 +35,16 @@ func (sm *stateMap) Delete(telegramID int64) { defer sm.mu.Unlock() delete(sm.m, telegramID) } + +// DeleteIfOneOf удаляет состояние только если оно совпадает с одним из переданных +func (sm *stateMap) DeleteIfOneOf(telegramID int64, states ...string) { + sm.mu.Lock() + defer sm.mu.Unlock() + cur := sm.m[telegramID] + for _, s := range states { + if cur == s { + delete(sm.m, telegramID) + return + } + } +}