diff --git a/app/app/Services/Sms/Providers/T2SmsProvider.php b/app/app/Services/Sms/Providers/T2SmsProvider.php index ee67ef6c..73a371f1 100644 --- a/app/app/Services/Sms/Providers/T2SmsProvider.php +++ b/app/app/Services/Sms/Providers/T2SmsProvider.php @@ -54,8 +54,7 @@ final class T2SmsProvider implements SmsDeliveryPayloadParser, SmsDeliveryReport public function send(SmsOutgoing $message): SmsSendResult { - // Имя отправителя: из сообщения (per-channel sender из БД), иначе дефолт канала. - $shortcode = $message->senderName !== '' ? $message->senderName : $this->naming; + $shortcode = $this->shortcodeFor($message->senderName); try { $response = Http::withBasicAuth($this->login, $this->password) @@ -102,6 +101,38 @@ final class T2SmsProvider implements SmsDeliveryPayloadParser, SmsDeliveryReport ); } + /** + * Имя отправителя для t2. + * + * 🔴 Имена регистрируются У КАЖДОГО ОПЕРАТОРА ОТДЕЛЬНО, и написание бывает + * разным: у МТС согласовано `liderra.ru`, у t2 — `Liderra.ru`. А клиентская + * рассылка несёт ОДНО имя на всех (`campaign.sender_name`), потому что имя + * там выбирается на кампанию, а не на канал (в модуле отдела продаж это уже + * сделано по каналу — `SalesSmsSender::activeByProviderKey`). + * + * Проверено живьём на бою 04.08.2026: `liderra.ru` → отказ + * `invalid_source_address`, `Liderra.ru` → принято. + * + * Поэтому приводим написание к согласованному с t2 — но ТОЛЬКО когда это то + * же самое имя, отличающееся регистром. Чужое имя не трогаем: подменить его + * значило бы соврать про отправителя, а честный отказ t2 лучше тихой подмены. + * + * 🪤 Это лечение половинчатое. Правильное — реестр имён по каналам и в + * клиентском модуле; тогда эта склейка станет не нужна. + */ + private function shortcodeFor(string $requested): string + { + if ($requested === '') { + return $this->naming; + } + + if ($this->naming !== '' && mb_strtolower($requested) === mb_strtolower($this->naming)) { + return $this->naming; + } + + return $requested; + } + /** * Пакетного запроса у t2 НЕТ — спрашивать можно только по одному сообщению. * Единица здесь не осторожность, а форма их API: адрес опроса несёт номер diff --git a/app/tests/Unit/Sms/T2SmsProviderTest.php b/app/tests/Unit/Sms/T2SmsProviderTest.php index c209da98..e643c84c 100644 --- a/app/tests/Unit/Sms/T2SmsProviderTest.php +++ b/app/tests/Unit/Sms/T2SmsProviderTest.php @@ -297,3 +297,49 @@ it('непонятную бумагу не разбирает — ноль за // Есть слово, но нет номера сообщения — правит нечего. ->and(t2_provider()->parseDeliveryPayload(['status' => 'delivered']))->toBe([]); }); + +/** + * 🔴 Имена отправителей регистрируются У КАЖДОГО ОПЕРАТОРА ОТДЕЛЬНО, и написание + * бывает разным: у МТС согласовано `liderra.ru`, у t2 — `Liderra.ru`. + * + * Клиентская рассылка несёт ОДНО имя на всех получателей (`campaign.sender_name`), + * и для t2 оно приезжает в чужом написании. Живой опыт на бою 04.08.2026: + * `liderra.ru` → отказ `invalid_source_address`, `Liderra.ru` → принято. + * + * Поэтому канал приводит написание к своему — но ТОЛЬКО когда это то же самое имя, + * отличающееся регистром. Чужое имя не трогаем: подменить его значило бы соврать + * про отправителя. + */ +it('имя отправителя приводит к написанию, согласованному с t2', function () { + Http::fake(['target.t2.ru/*' => Http::response(['result' => ['uid' => 'u'], 'status' => 'ok'], 200)]); + + t2_provider()->send(t2_message(sender: 'liderra')); + + Http::assertSent(fn ($request) => $request['shortcode'] === 'Liderra'); +}); + +it('точное совпадение имени не трогает', function () { + Http::fake(['target.t2.ru/*' => Http::response(['result' => ['uid' => 'u'], 'status' => 'ok'], 200)]); + + t2_provider()->send(t2_message(sender: 'Liderra')); + + Http::assertSent(fn ($request) => $request['shortcode'] === 'Liderra'); +}); + +it('чужое имя отправителя НЕ подменяет', function () { + // Когда у клиента появится своё согласованное имя, канал обязан слать его, + // а не наше. Отказ t2 в этом случае — честный ответ, а не повод врать. + Http::fake(['target.t2.ru/*' => Http::response(['result' => ['uid' => 'u'], 'status' => 'ok'], 200)]); + + t2_provider()->send(t2_message(sender: 'ChuzhoeImya')); + + Http::assertSent(fn ($request) => $request['shortcode'] === 'ChuzhoeImya'); +}); + +it('пустое имя заменяет своим', function () { + Http::fake(['target.t2.ru/*' => Http::response(['result' => ['uid' => 'u'], 'status' => 'ok'], 200)]); + + t2_provider()->send(t2_message(sender: '')); + + Http::assertSent(fn ($request) => $request['shortcode'] === 'Liderra'); +});