from typing import List, Optional from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.sql import func from db.models import UserBilling, UserPaymentMethod async def get_user_billing(session: AsyncSession, user_id: int) -> Optional[UserBilling]: stmt = select(UserBilling).where(UserBilling.user_id == user_id) result = await session.execute(stmt) return result.scalar_one_or_none() async def upsert_yk_payment_method( session: AsyncSession, *, user_id: int, payment_method_id: str, card_last4: Optional[str] = None, card_network: Optional[str] = None, ) -> UserBilling: existing = await get_user_billing(session, user_id) if existing: existing.yookassa_payment_method_id = payment_method_id existing.card_last4 = card_last4 existing.card_network = card_network existing.updated_at = func.now() await session.flush() await session.refresh(existing) return existing record = UserBilling( user_id=user_id, yookassa_payment_method_id=payment_method_id, card_last4=card_last4, card_network=card_network, ) session.add(record) await session.flush() await session.refresh(record) return record async def delete_yk_payment_method(session: AsyncSession, user_id: int) -> bool: existing = await get_user_billing(session, user_id) if not existing: return False existing.yookassa_payment_method_id = None existing.card_last4 = None existing.card_network = None existing.updated_at = func.now() await session.flush() await session.refresh(existing) return True # Multi-card support API async def upsert_user_payment_method( session: AsyncSession, *, user_id: int, provider_payment_method_id: str, provider: str = "yookassa", card_last4: Optional[str] = None, card_network: Optional[str] = None, set_default: bool = False, ) -> UserPaymentMethod: existing_stmt = select(UserPaymentMethod).where( UserPaymentMethod.provider_payment_method_id == provider_payment_method_id ) result = await session.execute(existing_stmt) existing: Optional[UserPaymentMethod] = result.scalar_one_or_none() if existing: existing.card_last4 = card_last4 existing.card_network = card_network if set_default: # unset previous defaults await session.execute( update(UserPaymentMethod) .where(UserPaymentMethod.user_id == user_id) .values(is_default=False) ) existing.is_default = True existing.updated_at = func.now() await session.flush() await session.refresh(existing) return existing if set_default: await session.execute( update(UserPaymentMethod) .where(UserPaymentMethod.user_id == user_id) .values(is_default=False) ) record = UserPaymentMethod( user_id=user_id, provider=provider, provider_payment_method_id=provider_payment_method_id, card_last4=card_last4, card_network=card_network, is_default=set_default, ) session.add(record) await session.flush() await session.refresh(record) return record async def list_user_payment_methods( session: AsyncSession, user_id: int, provider: Optional[str] = None ) -> List[UserPaymentMethod]: stmt = select(UserPaymentMethod).where(UserPaymentMethod.user_id == user_id) if provider: stmt = stmt.where(UserPaymentMethod.provider == provider) stmt = stmt.order_by(UserPaymentMethod.is_default.desc(), UserPaymentMethod.created_at.desc()) result = await session.execute(stmt) return result.scalars().all() async def get_user_default_payment_method( session: AsyncSession, user_id: int, provider: str = "yookassa" ) -> Optional[UserPaymentMethod]: stmt = select(UserPaymentMethod).where( UserPaymentMethod.user_id == user_id, UserPaymentMethod.provider == provider, UserPaymentMethod.is_default == True, ) result = await session.execute(stmt) return result.scalar_one_or_none() async def set_user_default_payment_method( session: AsyncSession, user_id: int, method_id: int ) -> bool: methods = await list_user_payment_methods(session, user_id) if not any(m.method_id == method_id for m in methods): return False await session.execute( update(UserPaymentMethod) .where(UserPaymentMethod.user_id == user_id) .values(is_default=False) ) await session.execute( update(UserPaymentMethod) .where(UserPaymentMethod.method_id == method_id) .values(is_default=True) ) return True async def delete_user_payment_method(session: AsyncSession, user_id: int, method_id: int) -> bool: stmt = select(UserPaymentMethod).where( UserPaymentMethod.method_id == method_id, UserPaymentMethod.user_id == user_id ) result = await session.execute(stmt) method = result.scalar_one_or_none() if not method: return False await session.delete(method) await session.flush() return True async def delete_user_payment_method_by_provider_id( session: AsyncSession, user_id: int, provider_payment_method_id: str, ) -> bool: """Delete a saved payment method by its provider payment_method.id for a specific user. Useful when callbacks pass the provider id (e.g., YooKassa pm_...) instead of our internal method_id. """ # noqa: E501 stmt = select(UserPaymentMethod).where( UserPaymentMethod.user_id == user_id, UserPaymentMethod.provider_payment_method_id == provider_payment_method_id, ) result = await session.execute(stmt) method: Optional[UserPaymentMethod] = result.scalar_one_or_none() if not method: return False await session.delete(method) await session.flush() return True async def user_has_saved_payment_method( session: AsyncSession, user_id: int, provider: str = "yookassa", ) -> bool: """Return True if the user has at least one saved payment method.""" try: methods = await list_user_payment_methods(session, user_id, provider) if methods: return True billing = await get_user_billing(session, user_id) return bool(billing and billing.yookassa_payment_method_id) except Exception: return False