mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-19 19:32:10 +00:00
Fix duplicate MulenPay transactions in history
This commit is contained in:
@@ -247,6 +247,7 @@ class MulenPayPaymentMixin:
|
||||
user,
|
||||
payment.amount_kopeks,
|
||||
f"Пополнение {display_name}: {payment.amount_kopeks // 100}₽",
|
||||
create_transaction=False,
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
from types import ModuleType, SimpleNamespace
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
@@ -25,6 +26,9 @@ class DummySession:
|
||||
async def commit(self) -> None: # pragma: no cover - метод вызывается, но без логики
|
||||
return None
|
||||
|
||||
async def refresh(self, *_args: Any, **_kwargs: Any) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class DummyLocalPayment:
|
||||
def __init__(self, payment_id: int = 501) -> None:
|
||||
@@ -139,3 +143,163 @@ async def test_create_mulenpay_payment_returns_none_without_service() -> None:
|
||||
description="Пополнение",
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.anyio("asyncio")
|
||||
async def test_process_mulenpay_callback_avoids_duplicate_transactions(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
service = _make_service(None)
|
||||
db = DummySession()
|
||||
|
||||
class DummyPayment:
|
||||
def __init__(self) -> None:
|
||||
self.user_id = 42
|
||||
self.amount_kopeks = 1500
|
||||
self.description = "Пополнение"
|
||||
self.uuid = "mulen_1_test"
|
||||
self.transaction_id: Optional[int] = None
|
||||
self.mulen_payment_id: Optional[int] = None
|
||||
self.status = "created"
|
||||
self.is_paid = False
|
||||
|
||||
payment = DummyPayment()
|
||||
|
||||
async def fake_get_mulenpay_payment_by_uuid(
|
||||
_db: DummySession, uuid: str
|
||||
) -> DummyPayment:
|
||||
assert uuid == payment.uuid
|
||||
return payment
|
||||
|
||||
async def fake_update_mulenpay_payment_status(
|
||||
_db: DummySession, **kwargs: Any
|
||||
) -> DummyPayment:
|
||||
payment.status = kwargs.get("status", payment.status)
|
||||
payment.mulen_payment_id = kwargs.get("mulen_payment_id", payment.mulen_payment_id)
|
||||
return payment
|
||||
|
||||
transaction_calls: list[Dict[str, Any]] = []
|
||||
|
||||
class DummyTransaction:
|
||||
def __init__(self, transaction_id: int = 555) -> None:
|
||||
self.id = transaction_id
|
||||
|
||||
async def fake_create_transaction(_db: DummySession, **kwargs: Any) -> DummyTransaction:
|
||||
transaction_calls.append(kwargs)
|
||||
return DummyTransaction()
|
||||
|
||||
async def fake_link_payment(
|
||||
db: DummySession, *, payment: DummyPayment, transaction_id: int
|
||||
) -> DummyPayment:
|
||||
payment.transaction_id = transaction_id
|
||||
return payment
|
||||
|
||||
class DummyUser:
|
||||
def __init__(self) -> None:
|
||||
self.id = payment.user_id
|
||||
self.telegram_id = 99
|
||||
self.balance_kopeks = 0
|
||||
self.has_made_first_topup = False
|
||||
self.language = "ru"
|
||||
self.promo_group = None
|
||||
self.subscription = None
|
||||
|
||||
dummy_user = DummyUser()
|
||||
|
||||
async def fake_get_user_by_id(_db: DummySession, user_id: int) -> DummyUser:
|
||||
assert user_id == payment.user_id
|
||||
return dummy_user
|
||||
|
||||
balance_call: Dict[str, Any] = {}
|
||||
|
||||
async def fake_add_user_balance(
|
||||
_db: DummySession,
|
||||
user: DummyUser,
|
||||
amount_kopeks: int,
|
||||
description: str,
|
||||
*,
|
||||
create_transaction: bool = True,
|
||||
**_kwargs: Any,
|
||||
) -> bool:
|
||||
balance_call.update(
|
||||
{
|
||||
"create_transaction": create_transaction,
|
||||
"description": description,
|
||||
"amount_kopeks": amount_kopeks,
|
||||
}
|
||||
)
|
||||
user.balance_kopeks += amount_kopeks
|
||||
return True
|
||||
|
||||
async def fake_process_referral_topup(*_args: Any, **_kwargs: Any) -> None:
|
||||
return None
|
||||
|
||||
async def fake_auto_purchase_saved_cart_after_topup(*_args: Any, **_kwargs: Any) -> bool:
|
||||
return False
|
||||
|
||||
async def fake_has_user_cart(*_args: Any, **_kwargs: Any) -> bool:
|
||||
return False
|
||||
|
||||
referral_module = ModuleType("app.services.referral_service")
|
||||
referral_module.process_referral_topup = fake_process_referral_topup # type: ignore[attr-defined]
|
||||
monkeypatch.setitem(sys.modules, "app.services.referral_service", referral_module)
|
||||
|
||||
auto_module = ModuleType("app.services.subscription_auto_purchase_service")
|
||||
auto_module.auto_purchase_saved_cart_after_topup = ( # type: ignore[attr-defined]
|
||||
fake_auto_purchase_saved_cart_after_topup
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "app.services.subscription_auto_purchase_service", auto_module)
|
||||
|
||||
user_cart_module = ModuleType("app.services.user_cart_service")
|
||||
user_cart_module.user_cart_service = SimpleNamespace( # type: ignore[attr-defined]
|
||||
has_user_cart=fake_has_user_cart
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "app.services.user_cart_service", user_cart_module)
|
||||
|
||||
monkeypatch.setattr(
|
||||
payment_service_module,
|
||||
"get_mulenpay_payment_by_uuid",
|
||||
fake_get_mulenpay_payment_by_uuid,
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
payment_service_module,
|
||||
"update_mulenpay_payment_status",
|
||||
fake_update_mulenpay_payment_status,
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
payment_service_module,
|
||||
"create_transaction",
|
||||
fake_create_transaction,
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
payment_service_module,
|
||||
"link_mulenpay_payment_to_transaction",
|
||||
fake_link_payment,
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
payment_service_module,
|
||||
"get_user_by_id",
|
||||
fake_get_user_by_id,
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
payment_service_module,
|
||||
"add_user_balance",
|
||||
fake_add_user_balance,
|
||||
raising=False,
|
||||
)
|
||||
|
||||
result = await service.process_mulenpay_callback(
|
||||
db,
|
||||
{"uuid": payment.uuid, "payment_status": "success", "id": 123, "amount": 1500},
|
||||
)
|
||||
|
||||
assert result is True
|
||||
assert transaction_calls, "create_transaction should be called"
|
||||
assert balance_call["create_transaction"] is False
|
||||
assert dummy_user.balance_kopeks == payment.amount_kopeks
|
||||
assert payment.transaction_id is not None
|
||||
|
||||
Reference in New Issue
Block a user