diff --git a/src/App.tsx b/src/App.tsx index 33f220f..fce6599 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -32,6 +32,7 @@ import { ErrorBoundary } from './components/ErrorBoundary'; import { PermissionRoute } from '@/components/auth/PermissionRoute'; import { saveReturnUrl } from './utils/token'; import { useAnalyticsCounters } from './hooks/useAnalyticsCounters'; +import { useSiteVerification } from './hooks/useSiteVerification'; // Auth pages - load immediately (small) import Login from './pages/Login'; import TelegramCallback from './pages/TelegramCallback'; @@ -229,6 +230,9 @@ function LegacySubscriptionRedirect() { function App() { useAnalyticsCounters(); + // Pulls site-verification tokens (Antilopay apay-tag etc.) from the bot + // backend and injects matching tags into document.head. + useSiteVerification(); return ( <> diff --git a/src/api/siteVerification.ts b/src/api/siteVerification.ts new file mode 100644 index 0000000..c11686b --- /dev/null +++ b/src/api/siteVerification.ts @@ -0,0 +1,16 @@ +import { apiClient } from './client'; + +export interface SiteVerification { + apay_tag: string | null; +} + +/** + * Public, unauthenticated endpoint — payment-provider crawlers (Antilopay) + * need to be able to read these values to validate site ownership. + */ +export const siteVerificationApi = { + async get(): Promise { + const { data } = await apiClient.get('/cabinet/public/site-verification'); + return data; + }, +}; diff --git a/src/hooks/useSiteVerification.ts b/src/hooks/useSiteVerification.ts new file mode 100644 index 0000000..b7b68db --- /dev/null +++ b/src/hooks/useSiteVerification.ts @@ -0,0 +1,56 @@ +import { useEffect } from 'react'; +import { siteVerificationApi } from '../api/siteVerification'; + +/** + * Fetches the configured site-verification tokens from the bot backend + * and injects the matching `` tags into `document.head`. + * + * Used by Antilopay's verification flow (lk.antilopay.com → Проект → + * Верификация → Способ 1: мета-тег). Once the merchant copies the + * Antilopay-provided value into `ANTILOPAY_APAY_VERIFICATION_TAG`, + * the cabinet will start rendering `` + * automatically — no rebuild required. + * + * The hook is no-op on backend errors (verification is a best-effort + * concern and must never block the cabinet from rendering). + */ +export function useSiteVerification(): void { + useEffect(() => { + let cancelled = false; + + siteVerificationApi + .get() + .then((data) => { + if (cancelled) return; + if (data.apay_tag) { + upsertMetaTag('apay-tag', data.apay_tag); + } else { + removeMetaTag('apay-tag'); + } + }) + .catch(() => { + // Silent fail — verification meta is best-effort, doesn't block UI. + }); + + return () => { + cancelled = true; + }; + }, []); +} + +function upsertMetaTag(name: string, content: string): void { + let tag = document.head.querySelector(`meta[name="${name}"]`); + if (!tag) { + tag = document.createElement('meta'); + tag.setAttribute('name', name); + document.head.appendChild(tag); + } + tag.setAttribute('content', content); +} + +function removeMetaTag(name: string): void { + const tag = document.head.querySelector(`meta[name="${name}"]`); + if (tag) { + tag.remove(); + } +}