Files
portal/app/config/services.php
T
Дмитрий 9234a9c2bc feat реклама показы: очередь заданий робота-грузчика, служебный канал и постановка при запуске
Портал ставит роботу задание, когда у баннеров ещё нет номеров креативов: вместо
ошибки клиент видит «готовим картинки», кампания остаётся черновиком, деньги не
морозятся. Робот берёт задания строго по одному — иначе слепки креативов до и
после перемешаются, и опознать их будет нельзя.

Канал робота закрыт своим сервис-токеном, внесён в исключения проверки CSRF и
отдаёт файл только того задания, которое сейчас в работе. Постановка задания
стоит внутри проверки рубильника Директа — при выключенном рубильнике портал в
Яндекс не ходит.

Права на новую таблицу выданы роли crm_admin_user: канал идёт через посредник
admin-db, подменяющий подключение. Нумератор выдан crm_app_user — он единственный
вставляет строки. Журнал схемы — запись v9.06.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:13:02 +03:00

389 lines
28 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.
|
*/
// Канал робота-грузчика креативов в веб-кабинет Яндекса. Свой токен, отдельный
// от клиентских и от токена «Поиск → Портал». Пусто → канал закрыт.
'creative_robot' => [
'token' => env('CREATIVE_ROBOT_TOKEN', ''),
],
// Рекламная аудитория кандидатов (Яндекс.Аудитории). Токен — OAuth Яндекс ID,
// выдаётся разово на аккаунт, где живёт сегмент. Пусто → синхронизация не идёт,
// джоб пишет причину в last_error и молчит.
'yandex_audience' => [
'token' => env('YANDEX_AUDIENCE_TOKEN'),
'segment_name' => env('YANDEX_AUDIENCE_SEGMENT_NAME', 'Лидерра — кандидаты'),
],
// Яндекс.Директ (B1) — рекламный кошелёк. ПЕСОЧНИЦА по умолчанию (В8): боевой —
// сменой env на api.direct.yandex.com после одобрения заявки. Денежный рубильник
// enabled: пока выкл — реальные вызовы Директа не идут (как enabled у прогрева).
'yandex_direct' => [
'base_url' => env('YANDEX_DIRECT_BASE_URL', 'https://api-sandbox.direct.yandex.com'),
'token' => env('YANDEX_DIRECT_TOKEN'),
'enabled' => env('YANDEX_DIRECT_ENABLED', false),
// Регионы показа по умолчанию (вся Россия = 225).
'region_ids' => [225],
// Предохранитель: SpendLimit в Яндексе = яндекс-бюджет × множитель (бэкстоп
// от перерасхода, НЕ клиентская цена).
'spend_limit_guard_multiplier' => (float) env('YANDEX_DIRECT_SPEND_GUARD', 1.2),
],
// Рекламная аудитория кандидатов в ВК (Task 8, 21.07.2026). Программный доступ
// запрошен 19.07, ждём проверки реквизитов — до получения токена джоб пишет
// vk_status=no_access и не делает ни одного обращения к ВК.
'vk_ads' => [
'token' => env('VK_ADS_TOKEN', ''),
],
'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'),
],
// СМС-центр (smsc.ru) — HLR-проверка номеров в Поиске клиентов (жив/несуществует).
// Баланс на плитке присмотра. Логин/пароль — те же, что у службы поиска.
'smsc' => [
'login' => env('SMSC_LOGIN'),
'password' => env('SMSC_PASSWORD'),
'balance_url' => env('SMSC_BALANCE_URL', 'https://smsc.ru/sys/balance.php'),
'red_floor_rub' => (int) env('SMSC_RED_FLOOR_RUB', 300),
'amber_floor_rub' => (int) env('SMSC_AMBER_FLOOR_RUB', 1000),
'topup_url' => env('SMSC_TOPUP_URL', 'https://smsc.ru/oplata/'),
],
// 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'),
],
// Яндекс.Метрика. Два счётчика:
// counter_id — кабинет (110494416), историческая аналитика кабинета.
// path_counter_id — «полный путь» (110476275), тот же счётчик, что на лендинге
// liderra.ru. Грузится на входе/регистрации/публичных страницах
// и в кабинете, чтобы Вебвизор видел цепочку от клика по рекламе
// до действий в кабинете одним человеком, а не двумя.
// Пусто = соответствующий счётчик не грузится.
// Админка и портал продаж Метрику не грузят вообще — см. router/index.ts.
'metrika' => [
'counter_id' => env('METRIKA_COUNTER_ID'),
'path_counter_id' => env('METRIKA_PATH_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')),
],
// Платёжный шлюз Ю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'),
],
'sms' => [
// Песочница: провайдеры не вызываются, отправка только помечается в журнале.
'sandbox' => (bool) env('SMS_SANDBOX', true),
// Резерв канала: точный оператор отказал → попробовать универсала.
// По умолчанию ВЫКЛ (решение владельца 23.07.2026) — включить одной строкой.
'failover_to_universal' => filter_var(env('SMS_FAILOVER_TO_UNIVERSAL', false), FILTER_VALIDATE_BOOL),
// Канал МТС = «МТС Маркетолог, Рассылки по своей базе PRO» (омни-адаптер
// api.mts.ru). naming — согласованное имя отправителя (MTSM_Test/liderra.ru);
// connect_to — мост (host:port SSH-туннеля, напр. 127.0.0.1:18443), пусто → напрямую.
'mts' => [
'token' => env('SMS_MTS_TOKEN'),
'enabled' => (bool) env('SMS_MTS_ENABLED', false),
'base_url' => env('SMS_MTS_BASE_URL', 'https://api.mts.ru/client-omni-adapter_production/1.0.2'),
'naming' => env('SMS_MTS_NAMING', ''),
'connect_to' => env('SMS_MTS_CONNECT_TO', ''),
],
'beeline' => [
'token' => env('SMS_BEELINE_TOKEN'),
'enabled' => (bool) env('SMS_BEELINE_ENABLED', false),
],
// Декларативный реестр живых каналов. Новый оператор = запись здесь
// + ключ в .env + (если API другой) файл-провайдер + арм в makeSmsProvider().
// Цены — в копейках за сегмент, ключ '*' = единая цена канала; меняются
// через .env без выката.
'providers' => [
'smsc' => [
'enabled' => (bool) env('SMS_SMSC_ENABLED', false),
'serves' => ['*'],
'price_kopecks' => ['*' => (int) env('SMS_SMSC_PRICE_KOP', 0)],
],
'mts' => [
'enabled' => (bool) env('SMS_MTS_ENABLED', false),
'serves' => ['mts'],
'price_kopecks' => ['*' => (int) env('SMS_MTS_PRICE_KOP', 0)],
],
],
],
];