From aa8bfc9d0859bcbe31de60f7127e5e606e146316 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 13 May 2026 10:50:31 +0300 Subject: [PATCH] feat(topup): direct-open payment page when method.open_url_direct is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User asked for the gift-style seamless flow on balance top-up: provider checkout opens inside Telegram MiniApp WebView without a click-to-open link panel. Made it an admin per-method toggle so it can be enabled selectively. src/pages/TopUpAmount.tsx — on topup mutation success: * Move saveTopUpPendingInfo to BEFORE any redirect so /balance/top-up/result can still pick up the pending payment after the provider's return_url fires * If method?.open_url_direct === true AND the URL is not a t.me/ deep link (Stars/CryptoBot), window.location.href = redirectUrl and return early * Otherwise fall through to the existing setPaymentUrl panel — preserves current behavior for methods without the flag enabled The t.me/ guard is important: window.location.href to a Telegram deep link inside a MiniApp WebView is unreliable (native shell cant always intercept). Those URLs continue to go through openTelegramLink / openInvoice in the panel path. Stars never reaches topUpMutation.onSuccess anyway (handled by the separate starsPaymentMutation); the guard is defense-in-depth for CryptoBot and any future t.me-deep-link providers. src/pages/AdminPaymentMethodEdit.tsx — added Open URL directly toggle with the same slider styling as the is_enabled toggle. Defaults to off. src/types/index.ts * PaymentMethod.open_url_direct?: boolean (user-facing) * PaymentMethodConfig.open_url_direct: boolean (admin shape) Translations added for ru/en/zh/fa with a hint clarifying behavior and the t.me/ exemption. --- src/locales/en.json | 2 ++ src/locales/fa.json | 2 ++ src/locales/ru.json | 2 ++ src/locales/zh.json | 2 ++ src/pages/AdminPaymentMethodEdit.tsx | 31 ++++++++++++++++++++++++++++ src/pages/TopUpAmount.tsx | 21 ++++++++++++++++--- src/types/index.ts | 4 ++++ 7 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 2af6e2e..dbb137c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1515,6 +1515,8 @@ "noMethods": "No payment methods configured", "methodEnabled": "Method enabled", "providerNotConfigured": "Provider not configured in env", + "openUrlDirect": "Open payment page directly", + "openUrlDirectHint": "Skip the link panel — the provider opens inside the MiniApp/tab right after the click. After payment the user returns to /balance/top-up/result. Ignored for Stars/CryptoBot and other t.me/ links.", "displayName": "Display name", "displayNameHint": "Leave empty for default name", "subOptions": "Payment options", diff --git a/src/locales/fa.json b/src/locales/fa.json index 854ed89..c26fb6d 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -2082,6 +2082,8 @@ "noMethods": "هیچ روش پرداخت پیکربندی‌شده‌ای نیست", "methodEnabled": "روش فعال است", "providerNotConfigured": "ارائه‌دهنده در env تنظیم نشده", + "openUrlDirect": "صفحه پرداخت را مستقیماً باز کن", + "openUrlDirectHint": "بدون پنل لینک — ارائه‌دهنده داخل MiniApp/تب بلافاصله پس از کلیک باز می‌شود. پس از پرداخت کاربر به /balance/top-up/result بازمی‌گردد. برای Stars/CryptoBot و سایر لینک‌های t.me/ این پرچم نادیده گرفته می‌شود.", "displayName": "نام نمایشی", "displayNameHint": "برای نام پیش‌فرض خالی بگذارید", "subOptions": "گزینه‌های پرداخت", diff --git a/src/locales/ru.json b/src/locales/ru.json index be768f7..394b2a0 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1537,6 +1537,8 @@ "noMethods": "Нет настроенных платёжных методов", "methodEnabled": "Метод включён", "providerNotConfigured": "Провайдер не настроен в env", + "openUrlDirect": "Открывать страницу оплаты сразу", + "openUrlDirectHint": "Без панели со ссылкой — провайдер открывается внутри MiniApp/вкладки сразу после клика. После оплаты юзер возвращается на /balance/top-up/result. Для Stars/CryptoBot и других t.me/-ссылок флаг игнорируется.", "displayName": "Отображаемое имя", "displayNameHint": "Оставьте пустым для имени по умолчанию", "subOptions": "Варианты оплаты", diff --git a/src/locales/zh.json b/src/locales/zh.json index 38ad838..dcac98d 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1280,6 +1280,8 @@ "noMethods": "没有配置的支付方法", "methodEnabled": "方法已启用", "providerNotConfigured": "提供商未在env中配置", + "openUrlDirect": "直接打开支付页面", + "openUrlDirectHint": "跳过链接面板 — 点击后提供商在 MiniApp/标签页内直接打开。支付完成后用户返回 /balance/top-up/result。Stars/CryptoBot 和其他 t.me/ 链接忽略此标志。", "displayName": "显示名称", "displayNameHint": "留空以使用默认名称", "subOptions": "支付选项", diff --git a/src/pages/AdminPaymentMethodEdit.tsx b/src/pages/AdminPaymentMethodEdit.tsx index 2f76918..f453f6d 100644 --- a/src/pages/AdminPaymentMethodEdit.tsx +++ b/src/pages/AdminPaymentMethodEdit.tsx @@ -66,6 +66,7 @@ export default function AdminPaymentMethodEdit() { const [firstTopupFilter, setFirstTopupFilter] = useState<'any' | 'yes' | 'no'>('any'); const [promoGroupFilterMode, setPromoGroupFilterMode] = useState<'all' | 'selected'>('all'); const [selectedPromoGroupIds, setSelectedPromoGroupIds] = useState([]); + const [openUrlDirect, setOpenUrlDirect] = useState(false); // Initialize state when config loads useEffect(() => { @@ -79,6 +80,7 @@ export default function AdminPaymentMethodEdit() { setFirstTopupFilter(config.first_topup_filter); setPromoGroupFilterMode(config.promo_group_filter_mode); setSelectedPromoGroupIds(config.allowed_promo_group_ids); + setOpenUrlDirect(config.open_url_direct); } }, [config]); @@ -100,6 +102,7 @@ export default function AdminPaymentMethodEdit() { first_topup_filter: firstTopupFilter, promo_group_filter_mode: promoGroupFilterMode, allowed_promo_group_ids: promoGroupFilterMode === 'selected' ? selectedPromoGroupIds : [], + open_url_direct: openUrlDirect, }; // Display name @@ -215,6 +218,34 @@ export default function AdminPaymentMethodEdit() { + {/* Open URL directly toggle */} +
+
+
+ {t('admin.paymentMethods.openUrlDirect', 'Открывать страницу оплаты сразу')} +
+
+ {t( + 'admin.paymentMethods.openUrlDirectHint', + 'Без панели со ссылкой — провайдер открывается внутри MiniApp/вкладки сразу после клика. После оплаты юзер возвращается на /balance/result.', + )} +
+
+ +
+ {/* Display name */}