feat(external): YooKassaLivenessProbe — живость платёжного шлюза
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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', '')),
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
Reference in New Issue
Block a user