feat(смс): канал СМС-центр (smsc.ru) — универсал
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Sms\Providers;
|
||||
|
||||
use App\Services\Sms\SmsOutgoing;
|
||||
use App\Services\Sms\SmsProvider;
|
||||
use App\Services\Sms\SmsSendException;
|
||||
use App\Services\Sms\SmsSendResult;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Канал smsc.ru — универсал: доставляет любому оператору. Учётка та же, что
|
||||
* уже используется для HLR-проверки номеров и плитки баланса (services.smsc).
|
||||
*
|
||||
* @param array<int, string> $serves
|
||||
* @param array<string, int> $priceKopecks
|
||||
*/
|
||||
final class SmscSmsProvider implements SmsProvider
|
||||
{
|
||||
private const SEND_URL = 'https://smsc.ru/sys/send.php';
|
||||
|
||||
public function __construct(
|
||||
private readonly string $login,
|
||||
private readonly string $password,
|
||||
private readonly array $serves,
|
||||
private readonly array $priceKopecks,
|
||||
) {}
|
||||
|
||||
public function key(): string
|
||||
{
|
||||
return 'smsc';
|
||||
}
|
||||
|
||||
public function servesOperators(): array
|
||||
{
|
||||
return $this->serves;
|
||||
}
|
||||
|
||||
public function priceKopecks(string $operator): int
|
||||
{
|
||||
return $this->priceKopecks[$operator] ?? $this->priceKopecks['*'] ?? 0;
|
||||
}
|
||||
|
||||
public function send(SmsOutgoing $message): SmsSendResult
|
||||
{
|
||||
try {
|
||||
$response = Http::get(self::SEND_URL, [
|
||||
'login' => $this->login,
|
||||
'psw' => $this->password,
|
||||
'phones' => $message->phone,
|
||||
'mes' => $message->body,
|
||||
'sender' => $message->senderName,
|
||||
'charset' => 'utf-8',
|
||||
'fmt' => 3,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
throw new SmsSendException('smsc.ru недоступен: '.$e->getMessage(), terminal: false);
|
||||
}
|
||||
|
||||
if ($response->serverError()) {
|
||||
throw new SmsSendException('smsc.ru вернул '.$response->status(), terminal: false);
|
||||
}
|
||||
|
||||
$body = (array) $response->json();
|
||||
|
||||
if (isset($body['error'])) {
|
||||
throw new SmsSendException(
|
||||
'smsc.ru отказал: '.(string) $body['error'],
|
||||
terminal: true,
|
||||
);
|
||||
}
|
||||
|
||||
return new SmsSendResult(
|
||||
providerMessageId: (string) ($body['id'] ?? ''),
|
||||
segments: $message->segments,
|
||||
costKopecks: $this->priceKopecks($message->operator) * $message->segments,
|
||||
acceptedAt: CarbonImmutable::now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Services\Sms\Providers\SmscSmsProvider;
|
||||
use App\Services\Sms\SmsOutgoing;
|
||||
use App\Services\Sms\SmsSendException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
function smsc_provider(): SmscSmsProvider
|
||||
{
|
||||
return new SmscSmsProvider('login', 'secret', ['*'], ['*' => 300]);
|
||||
}
|
||||
|
||||
function smsc_message(): SmsOutgoing
|
||||
{
|
||||
return new SmsOutgoing(
|
||||
phone: '79990000001',
|
||||
body: 'Здравствуйте!',
|
||||
senderName: 'liderra.ru',
|
||||
operator: 'МегаФон',
|
||||
segments: 1,
|
||||
);
|
||||
}
|
||||
|
||||
it('обслуживает всех как универсал', function () {
|
||||
expect(smsc_provider()->servesOperators())->toBe(['*'])
|
||||
->and(smsc_provider()->key())->toBe('smsc');
|
||||
});
|
||||
|
||||
it('успешная отправка возвращает id сообщения и цену по сегментам', function () {
|
||||
Http::fake(['smsc.ru/*' => Http::response(['id' => 555, 'cnt' => 1], 200)]);
|
||||
|
||||
$result = smsc_provider()->send(smsc_message());
|
||||
|
||||
expect($result->providerMessageId)->toBe('555')
|
||||
->and($result->costKopecks)->toBe(300);
|
||||
});
|
||||
|
||||
it('отказ smsc.ru бросает terminal-исключение', function () {
|
||||
Http::fake(['smsc.ru/*' => Http::response(['error' => 'invalid number', 'error_code' => 7], 200)]);
|
||||
|
||||
expect(fn () => smsc_provider()->send(smsc_message()))
|
||||
->toThrow(SmsSendException::class);
|
||||
});
|
||||
|
||||
it('сетевой сбой — не terminal (можно повторить)', function () {
|
||||
Http::fake(['smsc.ru/*' => Http::response('', 500)]);
|
||||
|
||||
try {
|
||||
smsc_provider()->send(smsc_message());
|
||||
$this->fail('ожидалось SmsSendException');
|
||||
} catch (SmsSendException $e) {
|
||||
expect($e->terminal)->toBeFalse();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user