feat(external): YooKassaLivenessProbe — живость платёжного шлюза

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-02 07:55:42 +03:00
parent dc392945e7
commit 31e7e2e77e
3 changed files with 82 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Services\External;
use Illuminate\Support\Facades\Http;
/**
* Живость платёжного шлюза ЮKassa: GET /v3/me под Basic-авторизацией магазина
* (shopId + секретный ключ). 200 жив. Денег не тратит (справочный эндпоинт).
* Нет ключей grey (нечего проверять).
*/
class YooKassaLivenessProbe implements LivenessProbe
{
public function serviceKey(): string
{
return 'yookassa';
}
public function check(): LivenessReading
{
$shopId = (string) config('services.yookassa.shop_id');
$secret = (string) config('services.yookassa.secret_key');
if ($shopId === '' || $secret === '') {
return LivenessReading::unknown('yookassa', 'ключи ЮKassa не заданы');
}
try {
$url = rtrim((string) config('services.yookassa.api_url', 'https://api.yookassa.ru/v3'), '/').'/me';
$resp = Http::withBasicAuth($shopId, $secret)->timeout(5)->acceptJson()->get($url);
if ($resp->ok()) {
return LivenessReading::alive('yookassa', 'шлюз отвечает');
}
return LivenessReading::down('yookassa', 'HTTP '.$resp->status());
} catch (\Throwable $e) {
return LivenessReading::down('yookassa', $e->getMessage());
}
}
}
+3
View File
@@ -116,6 +116,9 @@ return [
// опубликованными Ю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', '')),
+38
View File
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
use App\Services\External\YooKassaLivenessProbe;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
uses(TestCase::class); // нужен booted-app: config()/Http::fake()
beforeEach(function () {
config()->set('services.yookassa.shop_id', '1392092');
config()->set('services.yookassa.secret_key', 'test_secret');
config()->set('services.yookassa.api_url', 'https://api.yookassa.ru/v3');
});
it('зелёный при 200 от /me', function () {
Http::fake(['api.yookassa.ru/v3/me' => Http::response(['account_id' => '1392092'], 200)]);
$r = (new YooKassaLivenessProbe)->check();
expect($r->serviceKey)->toBe('yookassa');
expect($r->light)->toBe('green');
});
it('красный при 401', function () {
Http::fake(['api.yookassa.ru/v3/me' => Http::response(['type' => 'error'], 401)]);
expect((new YooKassaLivenessProbe)->check()->light)->toBe('red');
});
it('серый, когда ключи не заданы', function () {
config()->set('services.yookassa.shop_id', '');
config()->set('services.yookassa.secret_key', '');
expect((new YooKassaLivenessProbe)->check()->light)->toBe('grey');
});
it('красный при сетевой ошибке (проба не бросает)', function () {
Http::fake(fn () => throw new RuntimeException('network down'));
expect((new YooKassaLivenessProbe)->check()->light)->toBe('red');
});