feat(topup): direct-open payment page when method.open_url_direct is set

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.
This commit is contained in:
Fringg
2026-05-13 10:50:31 +03:00
parent 2a342f6adc
commit aa8bfc9d08
7 changed files with 61 additions and 3 deletions

View File

@@ -255,9 +255,8 @@ export default function TopUpAmount() {
onSuccess: (data) => {
const redirectUrl = data.payment_url || data.invoice_url;
if (redirectUrl) {
setPaymentUrl(redirectUrl);
// Save payment info for the result page
// Save payment info for the result page (do BEFORE possible redirect,
// иначе после window.location.href этот код не выполнится).
if (method && data.payment_id) {
const methodKey = method.id.toLowerCase().replace(/-/g, '_');
const displayName =
@@ -270,6 +269,22 @@ export default function TopUpAmount() {
created_at: Date.now(),
});
}
// open_url_direct: seamless флоу как при покупке подарка.
// window.location.href внутри Telegram MiniApp WebView навигирует
// в том же контейнере без открытия внешнего браузера. После
// оплаты return_url возвращает на /balance/top-up/result.
//
// t.me/ URL (Telegram Stars, CryptoBot) — всегда через нативный
// handler (openInvoice / openTelegramLink в setPaymentUrl-ветке).
// Stars уже отбит раньше через starsPaymentMutation, здесь — защита
// на случай CryptoBot и других Telegram-deep-link провайдеров.
if (method?.open_url_direct && !redirectUrl.startsWith('https://t.me/')) {
window.location.href = redirectUrl;
return;
}
setPaymentUrl(redirectUrl);
}
},
onError: (err: unknown) => {