Files
portal/app/config/services.php
T

302 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
// Капча самозаписи (G1/SP1). driver=null → NullCaptchaVerifier (dev/test).
// Реальный Yandex SmartCaptcha подключается позже (SP3/ops).
'captcha' => [
'driver' => env('CAPTCHA_DRIVER', 'null'),
'fake_passes' => filter_var(env('CAPTCHA_FAKE_PASSES', true), FILTER_VALIDATE_BOOL),
'yandex_server_key' => env('YANDEX_SMARTCAPTCHA_SERVER_KEY'),
'yandex_validate_url' => env('YANDEX_SMARTCAPTCHA_VALIDATE_URL', 'https://smartcaptcha.cloud.yandex.ru/validate'),
],
'supplier' => [
'login' => env('SUPPLIER_LOGIN'),
'password' => env('SUPPLIER_PASSWORD'),
'portal_url' => env('SUPPLIER_PORTAL_URL', 'https://crm.bp-gr.ru'),
'alert_email' => env('SUPPLIER_ALERT_EMAIL', 'ops@liderra.ru'),
// Плитка балансов (28.06): у кабинета поставщика НЕ денежный баланс, а остаток
// НОМЕРОВ («Баланс ГЦК» в выпадашке). Деньги = номера × number_price_rub (20 ₽/шт,
// подтверждено владельцем 28.06). avg_lead_price_rub=0 → расход/день неизвестен.
'number_price_rub' => (float) env('SUPPLIER_NUMBER_PRICE_RUB', 20),
'avg_lead_price_rub' => (float) env('SUPPLIER_AVG_LEAD_PRICE_RUB', 0),
'red_floor_rub' => (int) env('SUPPLIER_RED_FLOOR_RUB', 5000),
'amber_floor_rub' => (int) env('SUPPLIER_AMBER_FLOOR_RUB', 15000),
'topup_url' => env('SUPPLIER_TOPUP_URL', env('SUPPLIER_PORTAL_URL', 'https://crm.bp-gr.ru')),
// Рубильник ежедневной чистки доп-каналов (B6/B8/…). По умолчанию ВКЛ.
// Аварийное отключение: SUPPLIER_PRUNE_EXTRA_CHANNELS_ENABLED=false + рестарт воркеров.
'prune_extra_channels' => filter_var(env('SUPPLIER_PRUNE_EXTRA_CHANNELS_ENABLED', true), FILTER_VALIDATE_BOOL),
],
// DaData phone cleaner — резолв региона лида по телефону (lead region resolution).
// Ключи → YC Lockbox на проде; на dev/staging — .env. enabled=false до раскатки.
'dadata' => [
'api_key' => env('DADATA_API_KEY'),
'secret' => env('DADATA_SECRET'),
'timeout_ms' => (int) env('DADATA_TIMEOUT_MS', 2000),
'retries' => (int) env('DADATA_RETRIES', 1),
'daily_cap_rub' => (int) env('DADATA_DAILY_CAP_RUB', 10000),
'call_cost_kopecks' => (int) env('DADATA_CALL_COST_KOPECKS', 60), // ≈0.60 ₽/вызов, откалибровать по тарифу
'enabled' => filter_var(env('LEAD_REGION_RESOLVER_ENABLED', false), FILTER_VALIDATE_BOOL),
'cache_ttl_days' => (int) env('PHONE_REGION_CACHE_TTL_DAYS', 30),
// G1/SP2: подтяжка организации по ИНН (suggestions findById/party). Тот же api_key
// (Token), secret не нужен. Default false → NullPartyLookup (dev/тесты не ходят в сеть).
'party_enabled' => filter_var(env('DADATA_PARTY_ENABLED', false), FILTER_VALIDATE_BOOL),
// Плитка балансов (28.06): чтение баланса профиля + пороги светофора + ссылка пополнения.
'balance_url' => env('DADATA_BALANCE_URL', 'https://dadata.ru/api/v2/profile/balance'),
'red_floor_rub' => (int) env('DADATA_RED_FLOOR_RUB', 500),
'amber_floor_rub' => (int) env('DADATA_AMBER_FLOOR_RUB', 2000),
'topup_url' => env('DADATA_TOPUP_URL', 'https://dadata.ru/profile/#billing'),
],
// AITUNNEL (OpenAI-совместимый) для автоподбора: эмбеддинги (похожесть конкурента на
// профиль клиента) + LLM-классификатор «поставщик услуги или площадка-агрегатор?».
// Ключ — только в .env/окружении, НИКОГДА в гит. Пусто → движок отдаёт сырой список
// (без % и без авто-отсева площадок).
'aitunnel' => [
'key' => env('AITUNNEL_API_KEY'),
'base_url' => env('AITUNNEL_BASE_URL', 'https://api.aitunnel.ru/v1'),
'embed_model' => env('AITUNNEL_EMBED_MODEL', 'text-embedding-3-small'),
'chat_model' => env('AITUNNEL_CHAT_MODEL', 'gpt-4o-mini'),
// Модель отсева агрегаторов (вариант А — точнее, чтобы не терять живых конкурентов вроде
// «ГорЗайм»). Пусто → фолбэк на chat_model. Пример: AITUNNEL_AGGREGATOR_MODEL=gpt-4o.
'aggregator_model' => env('AITUNNEL_AGGREGATOR_MODEL'),
// ИИ-вызовы (анализ рубрик, эмбеддинги) — с ЗАПАСОМ: латентность модели плавает
// (один и тот же запрос то 8с, то 30-40с), а сбор асинхронный и клиент ждёт — режем
// медленный-но-настоящий ответ таймаутом ХУЖЕ, чем ждём. Потому большие таймауты.
'timeout_sec' => (int) env('AITUNNEL_TIMEOUT_SEC', 120),
// Батч отсева агрегаторов: список фирм режем на порции по aggregator_batch_size (иначе
// один запрос на ~100 фирм долгий и рискует не успеть) + отдельный долгий таймаут на порцию.
'aggregator_batch_size' => (int) env('AITUNNEL_AGGREGATOR_BATCH_SIZE', 40),
'aggregator_timeout_sec' => (int) env('AITUNNEL_AGGREGATOR_TIMEOUT_SEC', 240),
// Канал В — генератор имён (ZAFIKSIROVANO §0-БИС): одна рассуждающая модель sonar-reasoning-pro,
// в реальном прогоне ~30-70 с/вызов и плавает вверх; 2 прохода. Большой таймаут с запасом —
// лучше дождаться, чем прислать пусто (сбор асинхронный, клиент всё равно ждёт результат).
'research_model' => env('AITUNNEL_RESEARCH_MODEL', 'sonar-reasoning-pro'),
'research_timeout_sec' => (int) env('AITUNNEL_RESEARCH_TIMEOUT_SEC', 300),
// Плитка балансов (16.07): ручка баланса `/v1/aitunnel/balance` (найдена в доках
// aitunnel, отдаёт {"balance":N} в рублях) + пороги светофора + ссылка пополнения.
'balance_url' => env('AITUNNEL_BALANCE_URL', 'https://api.aitunnel.ru/v1/aitunnel/balance'),
'red_floor_rub' => (int) env('AITUNNEL_RED_FLOOR_RUB', 500),
'amber_floor_rub' => (int) env('AITUNNEL_AMBER_FLOOR_RUB', 2000),
'topup_url' => env('AITUNNEL_TOPUP_URL', 'https://aitunnel.ru/'),
],
// EXA — поиск САЙТА по имени для канала В (федералы): у федерала нет карточки в 2ГИС/Яндексе
// на регион, а нам нужен сайт (ZAFIKSIROVANO / §11.5). Ключ только в .env. Пусто → канал В без сайтов.
'exa' => [
'key' => env('EXA_API_KEY'),
'base_url' => env('EXA_BASE_URL', 'https://api.exa.ai'),
'timeout_sec' => (int) env('EXA_TIMEOUT_SEC', 90),
// Сколько запросов сайта-федерала слать ОДНОВРЕМЕННО (пул EXA). Лимит EXA — на КЛЮЧ,
// общий на всех клиентов, поэтому бережно; глобальный потолок между клиентами — очередь.
'concurrency' => (int) env('EXA_CONCURRENCY', 5),
// Прокси для EXA (Guzzle proxy option). EXA за Cloudflare режет российские IP, поэтому
// федеральный канал ходит наружу через зарубежный выход. Формат socks5h://host:port или
// http://host:port. Пусто → напрямую.
'proxy' => env('EXA_PROXY'),
// У EXA нет программной ручки баланса (биллинг только в кабинете) → плитка «жив/нет».
'balance_url' => env('EXA_BALANCE_URL'),
'red_floor_rub' => (int) env('EXA_RED_FLOOR_RUB', 500),
'amber_floor_rub' => (int) env('EXA_AMBER_FLOOR_RUB', 2000),
'topup_url' => env('EXA_TOPUP_URL', 'https://dashboard.exa.ai/'),
// Плитка балансов (16.07): нейтральный адрес для проверки живости SSH-тоннеля
// (запрос через exa.proxy на него). Не EXA — чтобы отделить «тоннель жив» от «EXA жив».
'tunnel_check_url' => env('EXA_TUNNEL_CHECK_URL', 'https://api.ipify.org'),
// «Пополнить» для EXA-тоннеля: за ним платный зарубежный VPN (клиент Happ). Провайдер
// подписки задаётся владельцем — если платишь не через Happ, переопредели EXA_TUNNEL_TOPUP_URL.
'tunnel_topup_url' => env('EXA_TUNNEL_TOPUP_URL', 'https://happ-proxy.com/'),
],
// Плитка балансов (28.06): Yandex Cloud биллинг (серверы + Managed PG ~18к/мес).
// OAuth владельца (interim) → IAM-токен → billing API. SA billing-reader создан,
// миграция на него — follow-up (только источник токена сменится). console_billing_url
// + billing_account_id строят прямую ссылку «Пополнить» в дашборде.
'yandex_cloud' => [
'oauth_token' => env('YC_OAUTH_TOKEN'),
'billing_account_id' => env('YC_BILLING_ACCOUNT_ID'),
'iam_url' => env('YC_IAM_URL', 'https://iam.api.cloud.yandex.net/iam/v1/tokens'),
'billing_url' => env('YC_BILLING_URL', 'https://billing.api.cloud.yandex.net/billing/v1/billingAccounts'),
'console_billing_url' => env('YC_CONSOLE_BILLING_URL', 'https://console.yandex.cloud/billing/accounts'),
'daily_spend_rub' => (int) env('YC_DAILY_SPEND_RUB', 600), // оценка ~18к/мес; откалибровать
'red_floor_rub' => (int) env('YC_RED_FLOOR_RUB', 1000),
'amber_floor_rub' => (int) env('YC_AMBER_FLOOR_RUB', 5000),
],
// G7-A: клиентская «Помощь».
'support' => [
'email' => env('SUPPORT_EMAIL', 'support@liderra.ru'),
],
// Яндекс.Метрика портала (lk.liderra.ru). Пусто = Метрика не грузится.
// Вебвизор пишет только кабинет клиента (см. resources/js/plugins/metrika.ts + router).
'metrika' => [
'counter_id' => env('METRIKA_COUNTER_ID'),
],
// ИИ-бот техподдержки в СВОЁМ окошке чата на сайте (спека 2026-07-13-own-chat-widget-design;
// до 07.07.2026 жил внутри чата Jivo — эта интеграция закрыта, см. docs/CHANGELOG).
// tours_enabled — «экскурсии»: ссылка «Показать на портале» в ответе бота. Раньше вели
// не по теме (совпало одно случайное слово) и были выключены. 13.07.2026 правило
// переписано: экскурсия только на ВОПРОС, слово которого попало в заголовок статьи
// или её синонимы, и только клиенту ИЗ КАБИНЕТА — гостю с лендинга там нечего смотреть.
'bot' => [
'tours_enabled' => env('BOT_TOURS_ENABLED', true),
// Показ примера на ЛЕНДИНГЕ для гостя (не вошедшего клиента): раздел «Как это
// работает» с якорями по темам (задача 2026-07-13). Гость не может открыть
// экскурсию внутри кабинета — ему нечего там показывать, но разбор на лендинге
// доступен без входа. Какие статьи имеют разбор — config/guides.php (связь по слагу).
'landing_url' => env('LANDING_URL', 'https://liderra.ru'),
// Мозг бота: yandex (данные не покидают РФ) | aitunnel (зарубежные модели через
// российский шлюз; ПДн вырезаются PersonalDataMask до отправки).
'llm' => env('BOT_LLM', 'yandex'),
'aitunnel_model' => env('BOT_AITUNNEL_MODEL', 'gpt-4o-mini'),
'timeout_seconds' => env('BOT_LLM_TIMEOUT', 10),
// Счётчики чата должны считать АТОМАРНО: файловый кэш под параллельной нагрузкой
// теряет до 74% прибавок (проверено), и защита от накрутки счёта становится дырявой.
// Redis (INCR) атомарен. В тестах — 'array' (один процесс, гонки не бывает).
'limiter_store' => env('BOT_LIMITER_STORE', 'redis'),
],
// YandexGPT Lite (Yandex Cloud Foundation Models) — мозг бота (решение 8 протокола).
'yandexgpt' => [
'api_key' => env('YANDEX_GPT_API_KEY', ''),
'folder_id' => env('YANDEX_GPT_FOLDER_ID', ''),
'model' => env('YANDEX_GPT_MODEL', 'yandexgpt-lite/latest'),
'endpoint' => env('YANDEX_GPT_ENDPOINT', 'https://llm.api.cloud.yandex.net/foundationModels/v1/completion'),
'timeout_seconds' => 8,
],
// Учёт посетителей: офлайн-базы гео (DB-IP Lite). Файлов нет → город не определяется,
// учёт продолжает работать. Обновление баз — раз в месяц, вручную.
'geoip' => [
'city_db' => env('GEOIP_CITY_DB', storage_path('geoip/dbip-city-lite.mmdb')),
'asn_db' => env('GEOIP_ASN_DB', storage_path('geoip/dbip-asn-lite.mmdb')),
],
// Яндекс.Метрика портала (lk.liderra.ru). Пусто = Метрика не грузится.
// Вебвизор пишет только кабинет клиента (см. resources/js/plugins/metrika.ts + router).
'metrika' => [
'counter_id' => env('METRIKA_COUNTER_ID'),
],
// Платёжный шлюз ЮKassa. webhook_ip_allowlist — CSV IP/CIDR из env (defense-in-depth
// на /api/webhook/payment). Пусто → fail-open (поток не ломается). На проде заполнить
// опубликованными ЮKassa подсетями: 185.71.76.0/27,185.71.77.0/27,77.75.153.0/25,
// 77.75.154.128/25,77.75.156.11,77.75.156.35,2a02:5180::/32.
'yookassa' => [
'shop_id' => env('YOOKASSA_SHOP_ID'),
'secret_key' => env('YOOKASSA_SECRET_KEY'),
'api_url' => env('YOOKASSA_API_URL', 'https://api.yookassa.ru/v3'),
'webhook_ip_allowlist' => array_values(array_filter(array_map(
'trim',
explode(',', (string) env('YOOKASSA_WEBHOOK_IPS', '')),
))),
],
// Автоподбор шаг 2: сервис обхода антибота справочников (2ГИС/Яндекс) — xfetch.ru.
// Ключ — ТОЛЬКО в .env (gitignored), в код/гит не попадает. Без ключа загрузчик
// молча возвращает пусто (поток не падает).
'xfetch' => [
'key' => env('XFETCH_API_KEY'),
// Несколько ключей через запятую (round-robin) — суммарная параллельность = ключи × concurrency.
// Пусто → берётся одиночный XFETCH_API_KEY. Ключи — ТОЛЬКО в .env, в код/гит не попадают.
'keys' => array_values(array_filter(
array_map('trim', explode(',', (string) env('XFETCH_API_KEYS', ''))),
static fn (string $v): bool => $v !== '',
)),
'endpoint' => env('XFETCH_ENDPOINT', 'https://xf4.ru/fetch'),
// Параллельность НА КЛЮЧ. Эмпирический предел xf4.ru — ~3 одновременных на ключ
// (выше идут 429 Too Many Requests; замер 01.07.2026). Меняется без деплоя через .env.
'concurrency' => (int) env('XFETCH_CONCURRENCY', 3),
'balance_url' => env('XFETCH_BALANCE_URL'),
'red_floor_rub' => (int) env('XFETCH_RED_FLOOR_RUB', 300),
'amber_floor_rub' => (int) env('XFETCH_AMBER_FLOOR_RUB', 1000),
'topup_url' => env('XFETCH_TOPUP_URL', 'https://xf4.ru/'),
],
// Автоподбор self-render: свой рендер-сервис на виртуалке (замена xf4, за флагом autopodbor.self_render).
// Секрет портал↔виртуалка и адрес — ТОЛЬКО в .env. Без них SelfRenderClient молча пусто (деградация).
'self_render' => [
'endpoint' => env('SELF_RENDER_ENDPOINT', ''),
'key' => env('SELF_RENDER_KEY', ''),
// Параллельность запросов к виртуалке (браузерная параллельность — внутри виртуалки, §12).
'concurrency' => (int) env('SELF_RENDER_CONCURRENCY', 8),
// Плитка балансов (16.07): URL health-check виртуалки. Пусто → пингуем endpoint.
'health_url' => env('SELF_RENDER_HEALTH_URL'),
],
// Плитка балансов (17.07): прокси автоподбора (Proxy.Market, мобильный Tele2). «Баланс» = срок
// действия. Живость проверяет виртуалка-рендер (у неё логин/пароль прокси) через /proxy-check.
// Ключ виртуалки берём из services.self_render.key. Срок — руками в .env при продлении.
'proxy_market' => [
'check_url' => env('PROXY_MARKET_CHECK_URL'), // https://51-250-1-97.sslip.io/proxy-check
'expires_at' => env('PROXY_MARKET_EXPIRES_AT'), // 2026-08-04
'amber_days' => (int) env('PROXY_MARKET_AMBER_DAYS', 7),
'topup_url' => env('PROXY_MARKET_TOPUP_URL', 'https://proxy.market/'),
],
// Куда слать алерты о недоступности/исходе денег внешних сервисов.
'monitoring' => [
'alert_email' => env('MONITORING_ALERT_EMAIL', env('SUPPLIER_ALERT_EMAIL', 'ops@liderra.ru')),
],
// Баланс Яндекс 360 (почта) — РУЧНОЙ ввод (см. Yandex360BalanceProvider). Пороги
// светофора — как у dadata; topup_url — страница «Оплата и тариф» в кабинете Яндекс 360.
'yandex360' => [
'red_floor_rub' => (int) env('YANDEX360_RED_FLOOR_RUB', 500),
'amber_floor_rub' => (int) env('YANDEX360_AMBER_FLOOR_RUB', 2000),
'topup_url' => env('YANDEX360_TOPUP_URL', 'https://admin.yandex.ru/products'),
],
// Плитка балансов (16.07): служба поиска клиентов отдела продаж (/opt/sales-finder, :8200
// за nginx /finder/). Только живость (2ГИС-пробив; денежного баланса нет). Пусто → не проверяем.
// Дефолт ПУСТОЙ намеренно: реальный URL задаётся только в прод-.env (SALES_FINDER_BASE_URL),
// иначе dev/тесты стучались бы в боевой /finder/. Прод-адрес — https://lk.liderra.ru/finder/.
'sales_finder' => [
'base_url' => env('SALES_FINDER_BASE_URL', ''),
'health_path' => env('SALES_FINDER_HEALTH_PATH', ''),
],
// Плитка балансов (16.07): Keyso (keys.so) — разведка рекламы, зависимость службы
// «Поиск клиентов» (/opt/sales-finder держит токен). Портал только проверяет живость
// хоста (пинг api.keys.so, <500 = жив), денег/токена не нужно.
'keyso' => [
'base_url' => env('KEYSO_BASE_URL', 'https://api.keys.so'),
'topup_url' => env('KEYSO_TOPUP_URL', 'https://www.keys.so/ru/tarif'),
],
];