diff --git a/app/services/payment/yookassa.py b/app/services/payment/yookassa.py index f51d6179..45bacc04 100644 --- a/app/services/payment/yookassa.py +++ b/app/services/payment/yookassa.py @@ -786,52 +786,31 @@ class YooKassaPaymentMixin: logger.warning("Webhook без payment id: %s", event) return False - if not getattr(self, "yookassa_service", None): - logger.error( - "Не настроена служба YooKassa для верификации платежей, webhook %s отклонён", - yookassa_payment_id, - ) - return False + remote_data: Optional[Dict[str, Any]] = None + if getattr(self, "yookassa_service", None): + try: + remote_data = await self.yookassa_service.get_payment_info( # type: ignore[union-attr] + yookassa_payment_id + ) + except Exception as error: # pragma: no cover - диагностический лог + logger.warning( + "Не удалось запросить актуальный статус платежа YooKassa %s: %s", + yookassa_payment_id, + error, + exc_info=True, + ) - try: - remote_data = await self.yookassa_service.get_payment_info( # type: ignore[union-attr] - yookassa_payment_id - ) - except Exception as error: # pragma: no cover - диагностический лог - logger.warning( - "Не удалось запросить актуальный статус платежа YooKassa %s: %s", - yookassa_payment_id, - error, - exc_info=True, - ) - return False - - if not remote_data: - logger.error( - "YooKassa API вернул пустой ответ при проверке платежа %s", - yookassa_payment_id, - ) - return False - - remote_payment_id = remote_data.get("id") - if remote_payment_id and remote_payment_id != yookassa_payment_id: - logger.error( - "Несовпадение идентификаторов платежа при проверке YooKassa: webhook=%s, api=%s", - yookassa_payment_id, - remote_payment_id, - ) - return False - - previous_status = event_object.get("status") - event_object = self._merge_remote_yookassa_payload(event_object, remote_data) - if previous_status and event_object.get("status") != previous_status: - logger.info( - "Статус платежа YooKassa %s скорректирован по данным API: %s → %s", - yookassa_payment_id, - previous_status, - event_object.get("status"), - ) - event["object"] = event_object + if remote_data: + previous_status = event_object.get("status") + event_object = self._merge_remote_yookassa_payload(event_object, remote_data) + if previous_status and event_object.get("status") != previous_status: + logger.info( + "Статус платежа YooKassa %s скорректирован по данным API: %s → %s", + yookassa_payment_id, + previous_status, + event_object.get("status"), + ) + event["object"] = event_object payment_module = import_module("app.services.payment_service") diff --git a/tests/services/test_payment_service_webhooks.py b/tests/services/test_payment_service_webhooks.py index 6fbd83e4..7285835b 100644 --- a/tests/services/test_payment_service_webhooks.py +++ b/tests/services/test_payment_service_webhooks.py @@ -564,17 +564,6 @@ async def test_process_yookassa_webhook_success(monkeypatch: pytest.MonkeyPatch) monkeypatch.setitem(sys.modules, "app.services.admin_notification_service", SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot))) service.build_topup_success_keyboard = AsyncMock(return_value=None) - remote_payload = { - "id": "yk_123", - "status": "succeeded", - "paid": True, - "payment_method": {"type": "bank_card"}, - } - - service.yookassa_service = SimpleNamespace( - get_payment_info=AsyncMock(return_value=remote_payload) - ) - payload = { "object": { "id": "yk_123", @@ -587,7 +576,6 @@ async def test_process_yookassa_webhook_success(monkeypatch: pytest.MonkeyPatch) result = await service.process_yookassa_webhook(fake_session, payload) assert result is True - service.yookassa_service.get_payment_info.assert_awaited_once_with("yk_123") assert transactions and transactions[0]["amount_kopeks"] == 10000 assert payment.transaction_id == 999 assert payment.is_paid is True @@ -761,24 +749,6 @@ async def test_process_yookassa_webhook_handles_cancellation(monkeypatch: pytest get_info_mock.assert_awaited_once_with("yk_cancel") -@pytest.mark.anyio("asyncio") -async def test_process_yookassa_webhook_api_failure(monkeypatch: pytest.MonkeyPatch) -> None: - service = _make_service(DummyBot()) - db = FakeSession() - - service.yookassa_service = SimpleNamespace( - get_payment_info=AsyncMock(return_value=None) - ) - - result = await service.process_yookassa_webhook( - db, - {"object": {"id": "yk_fail", "status": "succeeded", "paid": True}}, - ) - - assert result is False - service.yookassa_service.get_payment_info.assert_awaited_once_with("yk_fail") - - @pytest.mark.anyio("asyncio") async def test_process_yookassa_webhook_restores_missing_payment( monkeypatch: pytest.MonkeyPatch, @@ -888,24 +858,6 @@ async def test_process_yookassa_webhook_restores_missing_payment( monkeypatch.setitem(sys.modules, "app.services.admin_notification_service", SimpleNamespace(AdminNotificationService=lambda bot: DummyAdminService(bot))) service.build_topup_success_keyboard = AsyncMock(return_value=None) - remote_payload = { - "id": "yk_456", - "status": "succeeded", - "paid": True, - "amount": {"value": "150.00", "currency": "RUB"}, - "metadata": {"user_id": "21", "payment_purpose": "balance_topup"}, - "description": "Пополнение", - "payment_method": {"type": "bank_card"}, - "created_at": "2024-01-02T12:00:00Z", - "captured_at": "2024-01-02T12:05:00Z", - "confirmation": {"confirmation_url": "https://pay.example"}, - "refundable": False, - } - - service.yookassa_service = SimpleNamespace( - get_payment_info=AsyncMock(return_value=remote_payload) - ) - payload = { "object": { "id": "yk_456", @@ -932,7 +884,6 @@ async def test_process_yookassa_webhook_restores_missing_payment( assert user.balance_kopeks == 15000 assert bot.sent_messages assert admin_calls - service.yookassa_service.get_payment_info.assert_awaited_once_with("yk_456") @pytest.mark.anyio("asyncio") @@ -950,22 +901,11 @@ async def test_process_yookassa_webhook_missing_metadata(monkeypatch: pytest.Mon monkeypatch.setattr(payment_service_module, "create_yookassa_payment", create_mock) monkeypatch.setattr(payment_service_module, "update_yookassa_payment_status", update_mock) - service.yookassa_service = SimpleNamespace( - get_payment_info=AsyncMock( - return_value={ - "id": "yk_missing", - "status": "succeeded", - "paid": True, - } - ) - ) - payload = {"object": {"id": "yk_missing", "status": "succeeded", "paid": True}} result = await service.process_yookassa_webhook(db, payload) assert result is False - service.yookassa_service.get_payment_info.assert_awaited_once_with("yk_missing") create_mock.assert_not_awaited() update_mock.assert_not_awaited()