From 0d0978a656cc37aaa4c1f4401910fc2fdb37b0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 4 Aug 2026 18:15:31 +0300 Subject: [PATCH] =?UTF-8?q?fix(=D1=81=D0=BC=D1=81):=20=D0=A2=D0=B5=D0=BB?= =?UTF-8?q?=D0=B52=20=D0=BD=D0=B5=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8=D0=BC?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B8=D0=BC=D1=8F=20=D0=BE=D1=82=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=82=D0=B5=D0=BB=D1=8F=20=D0=B2=20=D1=87?= =?UTF-8?q?=D1=83=D0=B6=D0=BE=D0=BC=20=D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Первая клиентская рассылка на Теле2 упала: invalid_source_address. Причина не опечатка, а устройство: имена регистрируются У КАЖДОГО ОПЕРАТОРА ОТДЕЛЬНО, и написание разное — у МТС согласовано `liderra.ru`, у t2 `Liderra.ru`. Клиентская рассылка несёт ОДНО имя на всех получателей (campaign.sender_name): имя выбирается на кампанию, а не на канал. Для МТС оно верное, для t2 — нет. Проверено опытом на живом t2, а не рассуждением: имя [liderra.ru] → ОТКАЗ: invalid_source_address имя [Liderra.ru] → ПРИНЯТО, message-id-kDQU1vVzEMbT Лечение: канал приводит написание к согласованному с t2 — но ТОЛЬКО когда это то же самое имя, отличающееся регистром. Чужое имя не трогаем: подменить его значило бы соврать про отправителя, а честный отказ t2 лучше тихой подмены. Пустое имя, как и раньше, заменяется именем канала. 🪤 Лечение половинчатое и так и подписано в коде: правильное — реестр имён по каналам и в клиентском модуле (в модуле отдела продаж он уже есть, SalesSmsSender::activeByProviderKey). Тогда эта склейка станет не нужна. Четыре теста на разбор имени: тот же в другом регистре, точное совпадение, чужое имя, пустое. Всего 82 теста зелёные, Pint чист, статанализ по всему проекту 0 ошибок. Co-Authored-By: Claude Opus 5 --- .../Services/Sms/Providers/T2SmsProvider.php | 35 +++++++++++++- app/tests/Unit/Sms/T2SmsProviderTest.php | 46 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) 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'); +});