Files
remnawave-minishop/db/dal/payment_dal.py
T
machka-pasla f0bbe90f80 Add Docker build workflow and enhance admin functionalities
- Introduced a new GitHub Actions workflow for building and pushing the development Docker image.
- Added inline mode handling for user interactions, allowing users to share referral links and view statistics.
- Enhanced admin functionalities with new sections for user management, statistics, and promo code management.
- Implemented CSV export for logs and improved notification services for various events, including new user registrations and payment notifications.
- Updated localization files to support new features and commands.
2025-08-03 15:59:37 +03:00

222 lines
7.8 KiB
Python

import logging
from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy import update, func
from sqlalchemy.orm import selectinload
from db.models import Payment, User
async def create_payment_record(session: AsyncSession,
payment_data: Dict[str, Any]) -> Payment:
from .user_dal import get_user_by_id
user = await get_user_by_id(session, payment_data["user_id"])
if not user:
raise ValueError(
f"User with id {payment_data['user_id']} not found for creating payment."
)
if payment_data.get("promo_code_id"):
from .promo_code_dal import get_promo_code_by_id
promo = await get_promo_code_by_id(session,
payment_data["promo_code_id"])
if not promo:
raise ValueError(
f"Promo code with id {payment_data['promo_code_id']} not found."
)
new_payment = Payment(**payment_data)
session.add(new_payment)
await session.flush()
await session.refresh(new_payment)
logging.info(
f"Payment record {new_payment.payment_id} created for user {new_payment.user_id}"
)
return new_payment
async def get_payment_by_yookassa_id(
session: AsyncSession, yookassa_payment_id: str) -> Optional[Payment]:
stmt = select(Payment).where(
Payment.yookassa_payment_id == yookassa_payment_id)
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def get_payment_by_provider_payment_id(
session: AsyncSession, provider_payment_id: str) -> Optional[Payment]:
"""Fetch a payment by provider-specific identifier."""
stmt = select(Payment).where(
Payment.provider_payment_id == provider_payment_id)
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def get_payment_by_db_id(session: AsyncSession,
payment_db_id: int) -> Optional[Payment]:
stmt = select(Payment).where(Payment.payment_id == payment_db_id).options(
selectinload(Payment.user), selectinload(Payment.promo_code_used))
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def get_payment_by_db_id_with_promo(
session: AsyncSession, payment_db_id: int) -> Optional[Payment]:
stmt = select(Payment).where(Payment.payment_id == payment_db_id).options(
selectinload(Payment.promo_code_used))
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def update_payment_status_by_db_id(
session: AsyncSession,
payment_db_id: int,
new_status: str,
yk_payment_id: Optional[str] = None) -> Optional[Payment]:
payment = await get_payment_by_db_id(session, payment_db_id)
if payment:
payment.status = new_status
payment.updated_at = func.now()
if yk_payment_id and payment.yookassa_payment_id is None:
payment.yookassa_payment_id = yk_payment_id
await session.flush()
await session.refresh(payment)
logging.info(
f"Payment record {payment.payment_id} status updated to {new_status}."
)
else:
logging.warning(
f"Payment record with DB ID {payment_db_id} not found for status update."
)
return payment
async def user_has_successful_payment_for_provider(
session: AsyncSession, user_id: int, provider: str) -> bool:
"""Check if a user has at least one successful payment for the provider."""
stmt = (select(Payment.payment_id)
.where(Payment.user_id == user_id,
Payment.provider == provider,
Payment.status == 'succeeded')
.limit(1))
result = await session.execute(stmt)
return result.scalar_one_or_none() is not None
async def update_payment_status_by_yk_id(session: AsyncSession,
yookassa_payment_id: str,
new_status: str) -> Optional[Payment]:
payment = await get_payment_by_yookassa_id(session, yookassa_payment_id)
if payment:
payment.status = new_status
payment.updated_at = func.now()
await session.flush()
await session.refresh(payment)
logging.info(
f"Payment record with YK ID {yookassa_payment_id} status updated to {new_status}."
)
else:
logging.warning(
f"Payment record with YK ID {yookassa_payment_id} not found for status update."
)
return payment
async def get_recent_payment_logs_with_user(session: AsyncSession,
limit: int = 20,
offset: int = 0) -> List[Payment]:
stmt = (select(Payment).options(selectinload(Payment.user)).order_by(
Payment.created_at.desc()).limit(limit).offset(offset))
result = await session.execute(stmt)
return result.scalars().all()
async def update_provider_payment_and_status(
session: AsyncSession, payment_db_id: int,
provider_payment_id: str, new_status: str) -> Optional[Payment]:
payment = await get_payment_by_db_id(session, payment_db_id)
if payment:
payment.status = new_status
payment.provider_payment_id = provider_payment_id
payment.updated_at = func.now()
await session.flush()
await session.refresh(payment)
logging.info(
f"Payment record {payment.payment_id} updated with provider id {provider_payment_id} and status {new_status}."
)
else:
logging.warning(
f"Payment record with DB ID {payment_db_id} not found for provider update."
)
return payment
async def get_financial_statistics(session: AsyncSession) -> Dict[str, Any]:
"""Get comprehensive financial statistics."""
from datetime import datetime, timedelta
from sqlalchemy import and_, text
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=7)
month_start = today_start - timedelta(days=30)
# Today's revenue
stmt_today = select(func.sum(Payment.amount)).where(
and_(
Payment.status == 'succeeded',
Payment.created_at >= today_start
)
)
today_revenue = await session.execute(stmt_today)
today_amount = today_revenue.scalar() or 0
# Week revenue
stmt_week = select(func.sum(Payment.amount)).where(
and_(
Payment.status == 'succeeded',
Payment.created_at >= week_start
)
)
week_revenue = await session.execute(stmt_week)
week_amount = week_revenue.scalar() or 0
# Month revenue
stmt_month = select(func.sum(Payment.amount)).where(
and_(
Payment.status == 'succeeded',
Payment.created_at >= month_start
)
)
month_revenue = await session.execute(stmt_month)
month_amount = month_revenue.scalar() or 0
# All time revenue
stmt_all = select(func.sum(Payment.amount)).where(Payment.status == 'succeeded')
all_revenue = await session.execute(stmt_all)
all_amount = all_revenue.scalar() or 0
# Count of successful payments today
stmt_count_today = select(func.count(Payment.payment_id)).where(
and_(
Payment.status == 'succeeded',
Payment.created_at >= today_start
)
)
today_count = await session.execute(stmt_count_today)
today_payments_count = today_count.scalar() or 0
return {
"today_revenue": float(today_amount),
"week_revenue": float(week_amount),
"month_revenue": float(month_amount),
"all_time_revenue": float(all_amount),
"today_payments_count": today_payments_count
}