diff --git a/.env.example b/.env.example index f440273..687ea9d 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,13 @@ DISABLE_WELCOME_MESSAGE= # # Webhook Base URL (used for Telegram and payment providers) WEBHOOK_BASE_URL=https://webhooks.yourdomain.tld +# Payment Method Toggles +YOOKASSA_ENABLED=True # Turn on YOOKASSA +FREEKASSA_ENABLED=True # Turn on FreeKassa +STARS_ENABLED=True # Turn on STARS +TRIBUTE_ENABLED=True # Turn on TRIBUTE +CRYPTOPAY_ENABLED=True # Turn on CRYPTOPAY + # YooKassa Payment Gateway Configuration YOOKASSA_SHOP_ID=your_shop_id # Your store ID in YooKassa YOOKASSA_SECRET_KEY=your_secret_key # Your secret key for YooKassa @@ -33,7 +40,6 @@ YOOKASSA_VAT_CODE=1 # YOOKASSA_AUTOPAYMENTS_ENABLED=False # Auto-renew toggle # FreeKassa Payment Gateway Configuration -FREEKASSA_ENABLED=True # Turn on FreeKassa FREEKASSA_MERCHANT_ID=your_shop_id # Your shop ID in FreeKassa FREEKASSA_API_KEY=your_api_key # API key for REST requests FREEKASSA_SECOND_SECRET=your_second_secret # Secret word #2 (used to verify notifications) @@ -50,12 +56,6 @@ TRIBUTE_API_KEY= # TRIBUTE_SKIP_NOTIFICATIONS=True # Skip renewal notifications for Tribute payments TRIBUTE_SKIP_CANCELLATION_NOTIFICATIONS=False # Skip cancellation notifications for Tribute payments -# Payment Method Toggles -YOOKASSA_ENABLED=True # Turn on YOOKASSA -STARS_ENABLED=True # Turn on STARS -TRIBUTE_ENABLED=True # Turn on TRIBUTE -CRYPTOPAY_ENABLED=True # Turn on CRYPTOPAY - # Subscription Options. Specify cost parameters or payment links here. 1_MONTH_ENABLED=True RUB_PRICE_1_MONTH=150 diff --git a/bot/handlers/user/payment.py b/bot/handlers/user/payment.py index e6bc654..3c3ee9e 100644 --- a/bot/handlers/user/payment.py +++ b/bot/handlers/user/payment.py @@ -280,7 +280,7 @@ async def process_successful_payment(session: AsyncSession, bot: Bot, details_message = _("payment_successful_error_details") details_markup = get_connect_and_main_keyboard( - user_lang, i18n, settings, config_link + user_lang, i18n, settings, config_link, preserve_message=True ) try: await bot.send_message( diff --git a/bot/handlers/user/start.py b/bot/handlers/user/start.py index 52f902b..e3bc368 100644 --- a/bot/handlers/user/start.py +++ b/bot/handlers/user/start.py @@ -427,6 +427,13 @@ async def main_action_callback_handler( subscription_service, session, is_edit=True) + elif action == "back_to_main_keep": + await send_main_menu(callback, + settings, + i18n_data, + subscription_service, + session, + is_edit=False) else: i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") _ = lambda key, **kwargs: i18n.gettext( diff --git a/bot/handlers/user/subscription/payments.py b/bot/handlers/user/subscription/payments.py index 83b217e..ba69f91 100644 --- a/bot/handlers/user/subscription/payments.py +++ b/bot/handlers/user/subscription/payments.py @@ -1,5 +1,7 @@ import logging +from datetime import datetime from aiogram import Router, F, types +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession @@ -238,7 +240,13 @@ async def pay_yk_callback_handler(callback: types.CallbackQuery, settings: Setti await callback.message.edit_text( get_text(key="payment_link_message", months=months), - reply_markup=get_payment_url_keyboard(payment_response_yk["confirmation_url"], current_lang, i18n), + reply_markup=get_payment_url_keyboard( + payment_response_yk["confirmation_url"], + current_lang, + i18n, + back_callback=f"subscribe_period:{months}", + back_text_key="back_to_payment_methods_button", + ), disable_web_page_preview=False, ) else: @@ -354,7 +362,9 @@ async def pay_fk_callback_handler( if success: location = response_data.get("location") - provider_identifier = response_data.get("orderHash") or response_data.get("orderId") + order_hash = response_data.get("orderHash") + order_id_api = response_data.get("orderId") + provider_identifier = order_hash or order_id_api if provider_identifier: try: @@ -373,18 +383,36 @@ async def pay_fk_callback_handler( ) if location: + order_identifier_display = str(order_id_api or provider_identifier or payment_record.payment_id) + order_info_text = get_text( + "free_kassa_order_info", + order_id=order_identifier_display, + date=datetime.now().strftime("%Y-%m-%d"), + ) try: await callback.message.edit_text( - get_text(key="payment_link_message", months=months), - reply_markup=get_payment_url_keyboard(location, current_lang, i18n), + f"{order_info_text}\n\n" + get_text(key="payment_link_message", months=months), + reply_markup=get_payment_url_keyboard( + location, + current_lang, + i18n, + back_callback=f"subscribe_period:{months}", + back_text_key="back_to_payment_methods_button", + ), disable_web_page_preview=False, ) except Exception as e_edit: logging.warning(f"FreeKassa: failed to display payment link ({e_edit}), sending new message.") try: await callback.message.answer( - get_text(key="payment_link_message", months=months), - reply_markup=get_payment_url_keyboard(location, current_lang, i18n), + f"{order_info_text}\n\n" + get_text(key="payment_link_message", months=months), + reply_markup=get_payment_url_keyboard( + location, + current_lang, + i18n, + back_callback=f"subscribe_period:{months}", + back_text_key="back_to_payment_methods_button", + ), disable_web_page_preview=False, ) except Exception: @@ -481,14 +509,26 @@ async def pay_crypto_callback_handler( try: await callback.message.edit_text( get_text(key="payment_link_message", months=months), - reply_markup=get_payment_url_keyboard(invoice_url, current_lang, i18n), + reply_markup=get_payment_url_keyboard( + invoice_url, + current_lang, + i18n, + back_callback=f"subscribe_period:{months}", + back_text_key="back_to_payment_methods_button", + ), disable_web_page_preview=False, ) except Exception: try: await callback.message.answer( get_text(key="payment_link_message", months=months), - reply_markup=get_payment_url_keyboard(invoice_url, current_lang, i18n), + reply_markup=get_payment_url_keyboard( + invoice_url, + current_lang, + i18n, + back_callback=f"subscribe_period:{months}", + back_text_key="back_to_payment_methods_button", + ), disable_web_page_preview=False, ) except Exception: @@ -555,6 +595,18 @@ async def pay_stars_callback_handler( ) if payment_db_id: + try: + await callback.message.edit_text( + get_text("payment_invoice_sent_message", months=months), + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton( + text=get_text("back_to_payment_methods_button"), + callback_data=f"subscribe_period:{months}", + )] + ]), + ) + except Exception as e_edit: + logging.warning(f"Stars payment: failed to show invoice info message ({e_edit})") try: await callback.answer() except Exception: diff --git a/bot/keyboards/inline/user_keyboards.py b/bot/keyboards/inline/user_keyboards.py index 143f136..311ddfb 100644 --- a/bot/keyboards/inline/user_keyboards.py +++ b/bot/keyboards/inline/user_keyboards.py @@ -138,13 +138,20 @@ def get_payment_method_keyboard(months: int, price: float, return builder.as_markup() -def get_payment_url_keyboard(payment_url: str, lang: str, - i18n_instance) -> InlineKeyboardMarkup: +def get_payment_url_keyboard(payment_url: str, + lang: str, + i18n_instance, + back_callback: Optional[str] = None, + back_text_key: str = "back_to_main_menu_button" + ) -> InlineKeyboardMarkup: _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) builder = InlineKeyboardBuilder() builder.button(text=_(key="pay_button"), url=payment_url) - builder.button(text=_(key="back_to_main_menu_button"), - callback_data="main_action:back_to_main") + if back_callback: + builder.button(text=_(key=back_text_key), callback_data=back_callback) + else: + builder.button(text=_(key="back_to_main_menu_button"), + callback_data="main_action:back_to_main") builder.adjust(1) return builder.as_markup() @@ -192,7 +199,8 @@ def get_connect_and_main_keyboard( lang: str, i18n_instance, settings: Settings, - config_link: Optional[str]) -> InlineKeyboardMarkup: + config_link: Optional[str], + preserve_message: bool = False) -> InlineKeyboardMarkup: """Keyboard with a connect button and a back to main menu button.""" _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) builder = InlineKeyboardBuilder() @@ -216,10 +224,11 @@ def get_connect_and_main_keyboard( ) ) + back_callback = "main_action:back_to_main_keep" if preserve_message else "main_action:back_to_main" builder.row( InlineKeyboardButton( text=_("back_to_main_menu_button"), - callback_data="main_action:back_to_main", + callback_data=back_callback, ) ) diff --git a/bot/services/crypto_pay_service.py b/bot/services/crypto_pay_service.py index 7cf8baf..b846438 100644 --- a/bot/services/crypto_pay_service.py +++ b/bot/services/crypto_pay_service.py @@ -209,7 +209,9 @@ class CryptoPayService: end_date=final_end.strftime('%Y-%m-%d'), config_link=config_link) - markup = get_connect_and_main_keyboard(lang, i18n, settings, config_link) + markup = get_connect_and_main_keyboard( + lang, i18n, settings, config_link, preserve_message=True + ) try: await bot.send_message( user_id, diff --git a/bot/services/freekassa_service.py b/bot/services/freekassa_service.py index 132c875..f31e825 100644 --- a/bot/services/freekassa_service.py +++ b/bot/services/freekassa_service.py @@ -1,4 +1,5 @@ import asyncio +from datetime import datetime import hashlib import hmac import json @@ -358,8 +359,21 @@ class FreeKassaService: end_date=end_date_str, config_link=config_link, ) + if provider_payment_id: + order_info_text = _( + "free_kassa_order_full", + order_id=provider_payment_id, + date=datetime.now().strftime("%Y-%m-%d"), + ) + text = f"{order_info_text}\n{text}" - markup = get_connect_and_main_keyboard(lang, self.i18n, self.settings, config_link) + markup = get_connect_and_main_keyboard( + lang, + self.i18n, + self.settings, + config_link, + preserve_message=True, + ) try: await self.bot.send_message( payment.user_id, diff --git a/bot/services/stars_service.py b/bot/services/stars_service.py index 17173a2..e278796 100644 --- a/bot/services/stars_service.py +++ b/bot/services/stars_service.py @@ -148,7 +148,7 @@ class StarsService: config_link=config_link, ) markup = get_connect_and_main_keyboard( - current_lang, i18n, self.settings, config_link + current_lang, i18n, self.settings, config_link, preserve_message=True ) try: await self.bot.send_message( diff --git a/bot/services/tribute_service.py b/bot/services/tribute_service.py index de12e55..0e48ce7 100644 --- a/bot/services/tribute_service.py +++ b/bot/services/tribute_service.py @@ -208,7 +208,11 @@ class TributeService: config_link=config_link, ) markup = get_connect_and_main_keyboard( - lang, i18n, settings, config_link + lang, + i18n, + settings, + config_link, + preserve_message=True, ) try: diff --git a/locales/en.json b/locales/en.json index 60755c6..a881d6c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,7 +1,6 @@ { "welcome": "Welcome, {user_name}!", "main_menu_greeting": "Hi, {user_name}! 👋\nWhat would you like to do?", - "menu_activate_trial_button": "🆓 Free Trial", "menu_subscribe_inline": "🚀 Purchase", "menu_my_subscription_inline": "🔐 My Subscription", @@ -13,24 +12,20 @@ "menu_server_status_button": "📊 Status", "menu_support_button": "💬 Support", "menu_terms_button": "📄 Terms of Service", - "back_to_main_menu_button": "⬅️ Back", - "choose_language": "Choose language:", "language_set_alert": "Language changed!", - "error_occurred_try_again": "An error occurred, please try again.", "error_try_again": "Please try again.", "error_displaying_menu": "Error displaying menu.", "main_menu_unknown_action": "Unknown action.", - "select_subscription_period": "Select subscription period:", "subscribe_for_months_button": "{months} mo. - {price} {currency_symbol}", - "choose_payment_method": "Choose payment method:", "pay_button": "💳 Pay", "pay_with_yookassa_button": "💳 YooKassa", "pay_with_sbp_button": "📱 SBP", + "back_to_payment_methods_button": "⬅️ Back to payment methods", "pay_with_cryptopay_button": "💎 CryptoBot", "pay_with_tribute_button": "❤️ Tribute", "pay_with_stars_button": "🌟 Telegram Stars", @@ -38,13 +33,14 @@ "cancel_button": "❌ Cancel", "payment_description_subscription": "Subscription payment for {months} mo.", "payment_link_message": "To pay for {months} mo. subscription, click the button below:", + "free_kassa_order_info": "Order #{order_id} from {date}", + "payment_invoice_sent_message": "Telegram has sent the invoice above. Complete the payment or pick another method below.", "payment_successful_error_details": "✅ Payment succeeded, but an error occurred displaying details. Your subscription is active. Contact support if anything is wrong.", "payment_successful_full": "✅ Payment successful!\nYour {months}-month subscription is active until {end_date}.\n\nConnection key:\n{config_link}\n\nTo connect, open the link and follow the instructions 👇", "payment_successful_with_referral_bonus_full": "✅ Payment successful!\nYour {months}-month subscription (base end date: {base_end_date}) has been extended by {bonus_days} bonus days for referral from {inviter_name} and is now active until {final_end_date}.\n\nConnection key:\n{config_link}\n\nTo connect, open the link and follow the instructions 👇", "payment_failed": "❌ Payment failed or was cancelled. Please try again or contact support.", "config_link_not_available": "not available, contact support", "traffic_unlimited": "Unlimited", - "promo_code_prompt": "Please enter your promo code:", "promo_code_not_found": "Promo code {code} not found, expired, or already used the maximum number of times.", "promo_code_already_used_by_user": "You have already used promo code {code}.", @@ -66,7 +62,6 @@ "referral_bonus_inviter_notification_extended": "🎉 Congrats! Your friend {referee_name} paid for a subscription. You received {days} bonus days! Your subscription is now active until {new_end_date}.", "referral_bonus_inviter_notification_new_sub": "🎉 Congrats! Your friend {referee_name} paid for a subscription. You received a {days}-day bonus subscription! It is active until {new_end_date}.", "user_is_banned": "🚫 Your account is banned. Please contact support.", - "admin_panel_title": "Admin Panel", "admin_stats_button": "📊 Statistics", "admin_broadcast_button": "📢 Broadcast", @@ -323,7 +318,6 @@ "admin_user_subscription_active_until": "⏰ Active until:", "admin_user_subscription_error": "Loading error", "admin_promo_management_button": "🎟 Promo Management", - "admin_promo_management_title": "🎟 Promo Code Management\n\nSelect a promo code for detailed view:", "admin_promo_management_empty": "📭 No promo codes available", "admin_promo_card_title": "🎟 Promo Code: {code}", @@ -436,5 +430,6 @@ "admin_ads_delete_button": "🗑 Delete campaign", "admin_ads_delete_confirm": "Are you sure you want to delete campaign #{id}? This action is irreversible.", "admin_ads_deleted_success": "Campaign deleted.", - "admin_ads_not_found": "Campaign not found." -} + "admin_ads_not_found": "Campaign not found.", + "free_kassa_order_full": "Order #{order_id} from {date}\n\n" +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index 5664417..bcfe023 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,7 +1,6 @@ { "welcome": "Добро пожаловать, {user_name}!", "main_menu_greeting": "Привет, {user_name}! 👋\nЧто бы вы хотели сделать?", - "menu_activate_trial_button": "🆓 Пробный период", "menu_subscribe_inline": "🚀 Купить", "menu_my_subscription_inline": "🔐 Моя подписка", @@ -13,24 +12,20 @@ "menu_server_status_button": "📊 Статус", "menu_support_button": "💬 Поддержка", "menu_terms_button": "📄 Условия сервиса", - "back_to_main_menu_button": "⬅️ Назад", - "choose_language": "Выберите язык / Select language:", "language_set_alert": "Язык изменен!", - "error_occurred_try_again": "Произошла ошибка, попробуйте снова.", "error_try_again": "Попробуйте еще раз.", "error_displaying_menu": "Ошибка отображения меню.", "main_menu_unknown_action": "Неизвестное действие.", - "select_subscription_period": "Выберите срок подписки:", "subscribe_for_months_button": "{months} мес. - {price} {currency_symbol}", - "choose_payment_method": "Выберите способ оплаты:", "pay_button": "💳 Оплатить", "pay_with_yookassa_button": "💳 ЮKassa", "pay_with_sbp_button": "📱 СБП", + "back_to_payment_methods_button": "⬅️ Назад к выбору оплаты", "pay_with_cryptopay_button": "💎 CryptoBot", "pay_with_tribute_button": "❤️ Tribute", "pay_with_stars_button": "🌟 Звезды Telegram", @@ -38,13 +33,14 @@ "cancel_button": "❌ Отмена", "payment_description_subscription": "Оплата подписки на {months} мес.", "payment_link_message": "Для оплаты подписки на {months} мес., нажмите кнопку ниже:", + "free_kassa_order_info": "Заказ №{order_id} от {date}", + "payment_invoice_sent_message": "Счёт Telegram Stars отправлен выше. Нажмите «Оплатить» или вернитесь к выбору способа ниже.", "payment_successful_error_details": "✅ Оплата прошла успешно, но возникла ошибка при отображении деталей. Ваша подписка активна. Свяжитесь с поддержкой, если что-то не так.", "payment_successful_full": "✅ Оплата прошла успешно!\nВаша подписка на {months} мес. активна до {end_date}.\n\nКлюч подключения:\n{config_link}\n\nЧтобы подключиться, перейдите по ссылке и следуйте инструкции 👇", "payment_successful_with_referral_bonus_full": "✅ Оплата прошла успешно!\nВаша подписка на {months} мес. (базовая дата окончания: {base_end_date}) продлена на {bonus_days} бонусных дней за приглашение от {inviter_name} и теперь активна до {final_end_date}.\n\nКлюч подключения:\n{config_link}\n\nЧтобы подключиться, перейдите по ссылке и следуйте инструкции 👇", "payment_failed": "❌ Оплата не удалась или была отменена. Пожалуйста, попробуйте еще раз или свяжитесь с поддержкой.", "config_link_not_available": "недоступна, обратитесь в поддержку", "traffic_unlimited": "Безлимитный", - "promo_code_prompt": "Пожалуйста, введите ваш промокод:", "promo_code_not_found": "Промокод {code} не найден, истек или уже использован максимальное количество раз.", "promo_code_already_used_by_user": "Вы уже активировали промокод {code}.", @@ -66,7 +62,6 @@ "referral_bonus_inviter_notification_extended": "🎉 Поздравляем! Ваш друг {referee_name} оплатил подписку. Вам начислено {days} бонусных дней! Ваша подписка теперь активна до {new_end_date}.", "referral_bonus_inviter_notification_new_sub": "🎉 Поздравляем! Ваш друг {referee_name} оплатил подписку. Вам начислена бонусная подписка на {days} дней! Она активна до {new_end_date}.", "user_is_banned": "🚫 Ваш аккаунт заблокирован. Пожалуйста, свяжитесь со службой поддержки.", - "admin_panel_title": "Панель администратора", "admin_stats_button": "📊 Статистика", "admin_broadcast_button": "📢 Рассылка", @@ -322,7 +317,6 @@ "admin_user_subscription_active_until": "⏰ Действует до:", "admin_user_subscription_error": "Ошибка загрузки", "admin_promo_management_button": "🎟 Управление промокодами", - "admin_promo_management_title": "🎟 Управление промокодами\n\nВыберите промокод для детального просмотра:", "admin_promo_management_empty": "📭 Промокоды отсутствуют", "admin_promo_card_title": "🎟 Промокод: {code}", @@ -435,5 +429,6 @@ "admin_ads_delete_button": "🗑 Удалить кампанию", "admin_ads_delete_confirm": "Вы уверены, что хотите удалить кампанию #{id}? Это действие необратимо.", "admin_ads_deleted_success": "Кампания удалена.", - "admin_ads_not_found": "Кампания не найдена." -} + "admin_ads_not_found": "Кампания не найдена.", + "free_kassa_order_full": "Заказ №{order_id} от {date}\n\n" +} \ No newline at end of file diff --git a/Документация Freekassa Api.pdf b/Документация Freekassa Api.pdf deleted file mode 100644 index 77289d1..0000000 Binary files a/Документация Freekassa Api.pdf and /dev/null differ