194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
from datetime import UTC, datetime
|
|
|
|
import structlog
|
|
from sqlalchemy import select, update
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database.models import SavedPaymentMethod
|
|
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
async def create_saved_payment_method(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
yookassa_payment_method_id: str,
|
|
method_type: str = 'bank_card',
|
|
card_first6: str | None = None,
|
|
card_last4: str | None = None,
|
|
card_type: str | None = None,
|
|
card_expiry_month: str | None = None,
|
|
card_expiry_year: str | None = None,
|
|
title: str | None = None,
|
|
) -> SavedPaymentMethod | None:
|
|
"""Создаёт или реактивирует сохранённый метод оплаты."""
|
|
|
|
# Проверяем, есть ли уже такой метод (включая деактивированные)
|
|
result = await db.execute(
|
|
update(SavedPaymentMethod)
|
|
.where(
|
|
SavedPaymentMethod.yookassa_payment_method_id == yookassa_payment_method_id,
|
|
SavedPaymentMethod.user_id == user_id,
|
|
)
|
|
.values(
|
|
is_active=True,
|
|
method_type=method_type,
|
|
card_first6=card_first6,
|
|
card_last4=card_last4,
|
|
card_type=card_type,
|
|
card_expiry_month=card_expiry_month,
|
|
card_expiry_year=card_expiry_year,
|
|
title=title,
|
|
updated_at=datetime.now(UTC),
|
|
)
|
|
.returning(SavedPaymentMethod)
|
|
)
|
|
reactivated = result.scalar_one_or_none()
|
|
if reactivated:
|
|
await db.commit()
|
|
logger.info(
|
|
'Реактивирован сохранённый метод оплаты',
|
|
saved_method_id=reactivated.id,
|
|
user_id=user_id,
|
|
method_type=method_type,
|
|
card_last4=card_last4,
|
|
)
|
|
return reactivated
|
|
|
|
method = SavedPaymentMethod(
|
|
user_id=user_id,
|
|
yookassa_payment_method_id=yookassa_payment_method_id,
|
|
method_type=method_type,
|
|
card_first6=card_first6,
|
|
card_last4=card_last4,
|
|
card_type=card_type,
|
|
card_expiry_month=card_expiry_month,
|
|
card_expiry_year=card_expiry_year,
|
|
title=title,
|
|
)
|
|
|
|
db.add(method)
|
|
try:
|
|
await db.commit()
|
|
except IntegrityError as e:
|
|
await db.rollback()
|
|
logger.error(
|
|
'Ошибка создания сохранённого метода оплаты',
|
|
yookassa_payment_method_id=yookassa_payment_method_id,
|
|
user_id=user_id,
|
|
e=e,
|
|
)
|
|
return None
|
|
await db.refresh(method)
|
|
|
|
logger.info(
|
|
'Создан сохранённый метод оплаты',
|
|
saved_method_id=method.id,
|
|
user_id=user_id,
|
|
method_type=method_type,
|
|
card_last4=card_last4,
|
|
)
|
|
return method
|
|
|
|
|
|
async def get_active_payment_methods_by_user(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
) -> list[SavedPaymentMethod]:
|
|
"""Получить все активные сохранённые методы оплаты пользователя."""
|
|
result = await db.execute(
|
|
select(SavedPaymentMethod)
|
|
.where(
|
|
SavedPaymentMethod.user_id == user_id,
|
|
SavedPaymentMethod.is_active == True,
|
|
)
|
|
.order_by(SavedPaymentMethod.created_at.desc())
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def get_user_ids_with_active_payment_methods(
|
|
db: AsyncSession,
|
|
user_ids: list[int],
|
|
) -> set[int]:
|
|
"""Вернуть подмножество user_ids, у которых есть хотя бы один активный метод оплаты."""
|
|
if not user_ids:
|
|
return set()
|
|
result = await db.execute(
|
|
select(SavedPaymentMethod.user_id)
|
|
.where(
|
|
SavedPaymentMethod.user_id.in_(user_ids),
|
|
SavedPaymentMethod.is_active == True,
|
|
)
|
|
.distinct()
|
|
)
|
|
return set(result.scalars().all())
|
|
|
|
|
|
async def get_payment_method_by_yookassa_id(
|
|
db: AsyncSession,
|
|
yookassa_payment_method_id: str,
|
|
include_inactive: bool = False,
|
|
) -> SavedPaymentMethod | None:
|
|
"""Найти сохранённый метод по YooKassa payment_method.id."""
|
|
query = select(SavedPaymentMethod).where(
|
|
SavedPaymentMethod.yookassa_payment_method_id == yookassa_payment_method_id,
|
|
)
|
|
if not include_inactive:
|
|
query = query.where(SavedPaymentMethod.is_active == True)
|
|
result = await db.execute(query)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def deactivate_payment_method(
|
|
db: AsyncSession,
|
|
saved_method_id: int,
|
|
user_id: int,
|
|
) -> bool:
|
|
"""Деактивировать (soft-delete) сохранённый метод оплаты."""
|
|
result = await db.execute(
|
|
update(SavedPaymentMethod)
|
|
.where(
|
|
SavedPaymentMethod.id == saved_method_id,
|
|
SavedPaymentMethod.user_id == user_id,
|
|
SavedPaymentMethod.is_active == True,
|
|
)
|
|
.values(is_active=False, updated_at=datetime.now(UTC))
|
|
)
|
|
await db.commit()
|
|
|
|
if result.rowcount > 0:
|
|
logger.info(
|
|
'Метод оплаты деактивирован',
|
|
saved_method_id=saved_method_id,
|
|
user_id=user_id,
|
|
)
|
|
return True
|
|
return False
|
|
|
|
|
|
async def deactivate_all_user_payment_methods(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
) -> int:
|
|
"""Деактивировать все методы оплаты пользователя. Возвращает количество деактивированных."""
|
|
result = await db.execute(
|
|
update(SavedPaymentMethod)
|
|
.where(
|
|
SavedPaymentMethod.user_id == user_id,
|
|
SavedPaymentMethod.is_active == True,
|
|
)
|
|
.values(is_active=False, updated_at=datetime.now(UTC))
|
|
)
|
|
await db.commit()
|
|
|
|
if result.rowcount > 0:
|
|
logger.info(
|
|
'Все методы оплаты пользователя деактивированы',
|
|
user_id=user_id,
|
|
count=result.rowcount,
|
|
)
|
|
return result.rowcount
|