Implement multi-card payment method management and enhance user notifications
- Added support for saving multiple payment methods, allowing users to bind and manage their cards effectively. - Introduced a new paginated list view for displaying saved payment methods, improving user experience. - Enhanced user notifications for successful binding of payment methods, including localized messages. - Updated the database models and data access layer to accommodate multi-card functionality. - Refactored existing payment method handlers to integrate with the new multi-card system.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
from typing import Optional, Dict, Any
|
||||
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
|
||||
from db.models import UserBilling, UserPaymentMethod
|
||||
|
||||
|
||||
async def get_user_billing(session: AsyncSession, user_id: int) -> Optional[UserBilling]:
|
||||
@@ -52,3 +52,91 @@ async def delete_yk_payment_method(session: AsyncSession, user_id: int) -> bool:
|
||||
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
|
||||
|
||||
@@ -126,6 +126,24 @@ class UserBilling(Base):
|
||||
|
||||
user = relationship("User")
|
||||
|
||||
class UserPaymentMethod(Base):
|
||||
__tablename__ = "user_payment_methods"
|
||||
|
||||
method_id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.user_id"), nullable=False, index=True)
|
||||
provider = Column(String, nullable=False, default="yookassa", index=True)
|
||||
provider_payment_method_id = Column(String, nullable=False, unique=True, index=True)
|
||||
card_last4 = Column(String, nullable=True)
|
||||
card_network = Column(String, nullable=True)
|
||||
is_default = Column(Boolean, default=False, index=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
|
||||
|
||||
user = relationship("User")
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', 'provider_payment_method_id', name='uq_user_provider_method'),
|
||||
)
|
||||
|
||||
class PromoCode(Base):
|
||||
__tablename__ = "promo_codes"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user