Files
remnawave-minishop/db/dal/user_billing_dal.py
T
machka-pasla d83f64c5fe Refactor subscription handling and payment method management
- Updated the user router to import the subscription router directly, preparing for future package separation.
- Removed the outdated subscription handler file, streamlining the codebase and improving maintainability.
- Introduced a new method for deleting user payment methods by provider ID, enhancing flexibility in payment management.
- Improved error handling and clarity in payment method operations, ensuring a smoother user experience.
2025-09-04 19:21:14 +03:00

165 lines
5.8 KiB
Python

from typing import Optional, Dict, Any, List
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
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.
"""
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