From 835436fa1a8957e47a02b99ea2088d7757cef342 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Fri, 22 May 2026 16:29:59 +0300 Subject: [PATCH] fix: ensure web app pay button is spawning when enable payment provider --- .../bot/app/web/admin_api_impl/settings.py | 6 +++ backend/bot/app/web/webapp/cache_helpers.py | 36 ++++++++++++- tests/test_webapp_assets.py | 53 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/backend/bot/app/web/admin_api_impl/settings.py b/backend/bot/app/web/admin_api_impl/settings.py index 8b3299d..dbb2aa2 100644 --- a/backend/bot/app/web/admin_api_impl/settings.py +++ b/backend/bot/app/web/admin_api_impl/settings.py @@ -79,6 +79,12 @@ async def admin_settings_patch_route(request: web.Request) -> web.Response: if isinstance(cache, dict): cache["ts"] = 0.0 cache["data"] = {} + try: + from bot.app.web.webapp.cache_helpers import invalidate_all_webapp_user_caches + + await invalidate_all_webapp_user_caches(settings, include_devices=True) + except Exception: + logger.exception("Failed to invalidate WebApp user payload caches after settings update") if ( "WEBAPP_LOGO_URL" in updates or "WEBAPP_LOGO_URL" in deletes diff --git a/backend/bot/app/web/webapp/cache_helpers.py b/backend/bot/app/web/webapp/cache_helpers.py index 65ef57d..b6ca590 100644 --- a/backend/bot/app/web/webapp/cache_helpers.py +++ b/backend/bot/app/web/webapp/cache_helpers.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import Any, Awaitable, Callable, Optional -from bot.infra.redis import cache_delete, redis_key +from bot.infra.redis import cache_delete, cache_delete_pattern, redis_key from bot.utils.ttl_cache import AsyncTTLCache from config.settings import Settings @@ -55,6 +55,20 @@ def invalidate_local_webapp_user_payload( cache.invalidate(key) +def invalidate_all_local_webapp_user_payloads( + settings: Settings, + namespace: Optional[str] = None, +) -> None: + for (settings_id, cache_namespace, _ttl), cache in tuple( + _WEBAPP_USER_PAYLOAD_CACHES.items() + ): + if settings_id != id(settings): + continue + if namespace is not None and cache_namespace != namespace: + continue + cache.invalidate() + + async def invalidate_webapp_user_caches( settings: Settings, *user_ids: Optional[int], @@ -79,3 +93,23 @@ async def invalidate_webapp_user_caches( invalidate_local_webapp_user_payload(settings, "devices", user_id) if keys: await cache_delete(settings, *keys) + + +async def invalidate_all_webapp_user_caches( + settings: Settings, + *, + include_devices: bool = False, +) -> None: + namespaces = ["me"] + if include_devices: + namespaces.append("devices") + + for namespace in namespaces: + invalidate_all_local_webapp_user_payloads(settings, namespace) + try: + await cache_delete_pattern( + settings, + redis_key(settings, "cache", "webapp", namespace, "*"), + ) + except Exception: + continue diff --git a/tests/test_webapp_assets.py b/tests/test_webapp_assets.py index e8e139c..4879a11 100644 --- a/tests/test_webapp_assets.py +++ b/tests/test_webapp_assets.py @@ -15,6 +15,7 @@ from PIL import Image from bot.app.web import subscription_webapp from bot.app.web.admin_api_impl import themes as admin_themes from bot.app.web.webapp import assets as webapp_assets +from bot.app.web.webapp import cache_helpers from config.settings import Settings from config.webapp_themes_config import WebappThemesConfig, builtin_webapp_themes_config @@ -449,6 +450,58 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase): [{"id": "yookassa", "name": "Bank card", "icon": "WalletCards"}], ) + def test_serialize_payment_methods_includes_wata_from_provider_config(self): + from bot.payment_providers import build_provider_configs, get_provider_bundle + + build_provider_configs(force=True) + bundle = get_provider_bundle("wata_service") + self.assertIsNotNone(bundle) + bundle.config.ENABLED = True + bundle.config.API_TOKEN = "wata-token" + + settings = Settings( + _env_file=None, + BOT_TOKEN="token", + POSTGRES_USER="app_user", + POSTGRES_PASSWORD="app_password", + TARIFFS_CONFIG_PATH="missing-tariffs.json", + PAYMENT_METHODS_ORDER="wata", + STARS_ENABLED=False, + ) + app = {"wata_service": SimpleNamespace(configured=True)} + + methods = subscription_webapp._serialize_payment_methods(settings, app, "en") + + self.assertEqual(methods, [{"id": "wata", "name": "Wata", "icon": "WalletCards"}]) + + async def test_invalidate_all_webapp_user_caches_clears_cached_me_payload(self): + settings = Settings( + _env_file=None, + BOT_TOKEN="token", + POSTGRES_USER="app_user", + POSTGRES_PASSWORD="app_password", + REDIS_URL=None, + ) + calls = 0 + + async def loader(): + nonlocal calls + calls += 1 + return {"payment_methods": [{"id": f"method-{calls}"}]} + + first = await cache_helpers.webapp_cached_user_payload(settings, "me", 42, 60, loader) + second = await cache_helpers.webapp_cached_user_payload(settings, "me", 42, 60, loader) + + self.assertEqual(first, {"payment_methods": [{"id": "method-1"}]}) + self.assertEqual(second, first) + self.assertEqual(calls, 1) + + await cache_helpers.invalidate_all_webapp_user_caches(settings) + third = await cache_helpers.webapp_cached_user_payload(settings, "me", 42, 60, loader) + + self.assertEqual(third, {"payment_methods": [{"id": "method-2"}]}) + self.assertEqual(calls, 2) + def test_serialize_plans_includes_stars_only_subscription_options(self): settings = Settings( _env_file=None,