fix(payments): open_url_direct payment URL opens externally in Telegram (#654272)

The reported #654272 case ('функция немедленного открытия ссылки', reproduced on RollyPay AND YooKassa, fine on desktop) is the open_url_direct flow — not the connection deep-link opener fixed in 325e221.

When a payment method has open_url_direct, the cabinet did window.location.href = payment_url INSIDE the Telegram in-app WebView. SBP/RollyPay/YooKassa pages then hand off to a bank app via a custom scheme, which the WebView can't open: Android shows net::ERR_UNKNOWN_URL_SCHEME, iOS opens nothing ('приложение не определяется'); link generation logs fine. Desktop works because it's a real browser.

Add openPaymentUrl(): in Telegram open via openLink (external browser — the OS hands off to the bank app, return_url brings the user back); on web keep same-tab navigation (no popup blocker). Applied to TopUpAmount (top-up) and GiftSubscription (gift purchase). QuickPurchase is a web landing page (no platform abstraction) and is unaffected.
This commit is contained in:
c0mrade
2026-06-08 14:21:45 +03:00
parent 325e221e32
commit ce5737fcbc
3 changed files with 35 additions and 3 deletions

View File

@@ -22,6 +22,7 @@ import { getApiErrorMessage } from '../utils/api-error';
import { formatPrice } from '../utils/format'; import { formatPrice } from '../utils/format';
import { useCurrency } from '../hooks/useCurrency'; import { useCurrency } from '../hooks/useCurrency';
import { usePlatform, useHaptic } from '@/platform'; import { usePlatform, useHaptic } from '@/platform';
import { openPaymentUrl } from '../utils/openPaymentUrl';
import { import {
SparklesIcon, SparklesIcon,
GiftIcon, GiftIcon,
@@ -388,7 +389,7 @@ function BuyTabContent({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { openInvoice, capabilities } = usePlatform(); const { openInvoice, capabilities, openLink, platform } = usePlatform();
const haptic = useHaptic(); const haptic = useHaptic();
// Selection state // Selection state
@@ -481,7 +482,9 @@ function BuyTabContent({
} }
return; return;
} }
window.location.href = result.payment_url; // Non-Stars provider (RollyPay/YooKassa/SBP …): open externally inside Telegram so
// a bank-app hand-off via custom scheme doesn't dead-end in the WebView (#654272).
openPaymentUrl(result.payment_url, platform, openLink);
} else { } else {
// Balance purchase: switch to MyGifts tab so the new code is visible // Balance purchase: switch to MyGifts tab so the new code is visible
queryClient.invalidateQueries({ queryKey: ['balance'] }); queryClient.invalidateQueries({ queryKey: ['balance'] });

View File

@@ -14,6 +14,7 @@ import type { PaymentMethod, PaymentMethodOption } from '../types';
import BentoCard from '../components/ui/BentoCard'; import BentoCard from '../components/ui/BentoCard';
import { saveTopUpPendingInfo } from '../utils/topUpStorage'; import { saveTopUpPendingInfo } from '../utils/topUpStorage';
import { getSafeRedirectPath } from '../utils/safeRedirect'; import { getSafeRedirectPath } from '../utils/safeRedirect';
import { openPaymentUrl } from '../utils/openPaymentUrl';
import { copyToClipboard } from '@/utils/clipboard'; import { copyToClipboard } from '@/utils/clipboard';
import { import {
CardIcon, CardIcon,
@@ -245,7 +246,11 @@ export default function TopUpAmount() {
lowerUrl.startsWith('http://t.me/') || lowerUrl.startsWith('http://t.me/') ||
lowerUrl.startsWith('tg://'); lowerUrl.startsWith('tg://');
if (method?.open_url_direct && !isTelegramDeepLink) { if (method?.open_url_direct && !isTelegramDeepLink) {
window.location.href = redirectUrl; // In the Telegram WebView, same-container navigation to the provider page breaks
// when it hands off to a bank app via a custom scheme (SBP) — Android shows
// ERR_UNKNOWN_URL_SCHEME, iOS opens nothing (bug #654272). Open externally there;
// on web keep same-tab navigation.
openPaymentUrl(redirectUrl, platform, openLink);
return; return;
} }

View File

@@ -0,0 +1,24 @@
/**
* Open a payment-provider URL the right way for the current platform.
*
* In the Telegram in-app WebView, navigating the SAME container to the provider page
* (window.location.href) breaks when that page hands off to a bank app via a custom scheme
* — SBP/RollyPay/YooKassa do this. Android then shows a full-page net::ERR_UNKNOWN_URL_SCHEME
* and iOS opens nothing ("приложение не определяется"), even though link generation succeeded
* (Telegram bug #654272). Opening in the EXTERNAL browser (openLink) lets the OS hand off to
* the bank app, and the provider's return_url brings the user back.
*
* On the web platform a real browser handles the hand-off inline, so same-tab navigation is
* correct — and it isn't popup-blocked the way window.open() from an async callback would be.
*/
export function openPaymentUrl(
url: string,
platform: string,
openLink: (url: string) => void,
): void {
if (platform === 'telegram') {
openLink(url);
} else {
window.location.href = url;
}
}