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