mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-02 16:20:49 +00:00
Add files via upload
This commit is contained in:
@@ -7,6 +7,7 @@ from .admin_ban_system import router as admin_ban_system_router
|
||||
from .admin_email_templates import router as admin_email_templates_router
|
||||
from .admin_broadcasts import router as admin_broadcasts_router
|
||||
from .admin_campaigns import router as admin_campaigns_router
|
||||
from .admin_payment_methods import router as admin_payment_methods_router
|
||||
from .admin_payments import router as admin_payments_router
|
||||
from .admin_promo_offers import router as admin_promo_offers_router
|
||||
from .admin_promocodes import promo_groups_router as admin_promo_groups_router, router as admin_promocodes_router
|
||||
@@ -77,6 +78,7 @@ router.include_router(admin_promocodes_router)
|
||||
router.include_router(admin_promo_groups_router)
|
||||
router.include_router(admin_campaigns_router)
|
||||
router.include_router(admin_users_router)
|
||||
router.include_router(admin_payment_methods_router)
|
||||
router.include_router(admin_payments_router)
|
||||
router.include_router(admin_promo_offers_router)
|
||||
router.include_router(admin_remnawave_router)
|
||||
|
||||
228
app/cabinet/routes/admin_payment_methods.py
Normal file
228
app/cabinet/routes/admin_payment_methods.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""Admin routes for payment method configuration in cabinet."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database.models import User
|
||||
from app.services.payment_method_config_service import (
|
||||
_get_method_defaults,
|
||||
get_all_configs,
|
||||
get_all_promo_groups,
|
||||
get_config_by_method_id,
|
||||
update_config,
|
||||
update_sort_order,
|
||||
)
|
||||
|
||||
from ..dependencies import get_cabinet_db, get_current_admin_user
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix='/admin/payment-methods', tags=['Cabinet Admin Payment Methods'])
|
||||
|
||||
|
||||
# ============ Schemas ============
|
||||
|
||||
|
||||
class SubOptionInfo(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class PaymentMethodConfigResponse(BaseModel):
|
||||
method_id: str
|
||||
sort_order: int
|
||||
is_enabled: bool
|
||||
display_name: str | None = None
|
||||
default_display_name: str
|
||||
sub_options: dict | None = None
|
||||
available_sub_options: list[SubOptionInfo] | None = None
|
||||
min_amount_kopeks: int | None = None
|
||||
max_amount_kopeks: int | None = None
|
||||
default_min_amount_kopeks: int
|
||||
default_max_amount_kopeks: int
|
||||
user_type_filter: str
|
||||
first_topup_filter: str
|
||||
promo_group_filter_mode: str
|
||||
allowed_promo_group_ids: list[int] = Field(default_factory=list)
|
||||
is_provider_configured: bool
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PaymentMethodConfigUpdateRequest(BaseModel):
|
||||
is_enabled: bool | None = None
|
||||
display_name: str | None = Field(default=None, description='Null to reset to default')
|
||||
sub_options: dict | None = None
|
||||
min_amount_kopeks: int | None = Field(default=None, ge=0)
|
||||
max_amount_kopeks: int | None = Field(default=None, ge=0)
|
||||
user_type_filter: str | None = Field(default=None, pattern='^(all|telegram|email)$')
|
||||
first_topup_filter: str | None = Field(default=None, pattern='^(any|yes|no)$')
|
||||
promo_group_filter_mode: str | None = Field(default=None, pattern='^(all|selected)$')
|
||||
allowed_promo_group_ids: list[int] | None = None
|
||||
# Allow explicitly resetting display_name to null
|
||||
reset_display_name: bool = False
|
||||
reset_min_amount: bool = False
|
||||
reset_max_amount: bool = False
|
||||
|
||||
|
||||
class SortOrderRequest(BaseModel):
|
||||
method_ids: list[str]
|
||||
|
||||
|
||||
class PromoGroupSimple(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============ Helpers ============
|
||||
|
||||
|
||||
def _enrich_config(config, defaults: dict) -> PaymentMethodConfigResponse:
|
||||
"""Enrich a PaymentMethodConfig with env-var defaults."""
|
||||
method_def = defaults.get(config.method_id, {})
|
||||
|
||||
available_sub_options = None
|
||||
raw_options = method_def.get('available_sub_options')
|
||||
if raw_options:
|
||||
available_sub_options = [SubOptionInfo(**opt) for opt in raw_options]
|
||||
|
||||
return PaymentMethodConfigResponse(
|
||||
method_id=config.method_id,
|
||||
sort_order=config.sort_order,
|
||||
is_enabled=config.is_enabled,
|
||||
display_name=config.display_name,
|
||||
default_display_name=method_def.get('default_display_name', config.method_id),
|
||||
sub_options=config.sub_options,
|
||||
available_sub_options=available_sub_options,
|
||||
min_amount_kopeks=config.min_amount_kopeks,
|
||||
max_amount_kopeks=config.max_amount_kopeks,
|
||||
default_min_amount_kopeks=method_def.get('default_min', 1000),
|
||||
default_max_amount_kopeks=method_def.get('default_max', 10000000),
|
||||
user_type_filter=config.user_type_filter,
|
||||
first_topup_filter=config.first_topup_filter,
|
||||
promo_group_filter_mode=config.promo_group_filter_mode,
|
||||
allowed_promo_group_ids=[pg.id for pg in config.allowed_promo_groups],
|
||||
is_provider_configured=method_def.get('is_configured', False),
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
|
||||
|
||||
# ============ Routes ============
|
||||
|
||||
|
||||
@router.get('', response_model=list[PaymentMethodConfigResponse])
|
||||
async def list_payment_methods(
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""List all payment method configurations."""
|
||||
configs = await get_all_configs(db)
|
||||
defaults = _get_method_defaults()
|
||||
return [_enrich_config(c, defaults) for c in configs]
|
||||
|
||||
|
||||
@router.get('/promo-groups', response_model=list[PromoGroupSimple])
|
||||
async def list_promo_groups(
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""List all promo groups for filter selector."""
|
||||
groups = await get_all_promo_groups(db)
|
||||
return [PromoGroupSimple(id=g.id, name=g.name) for g in groups]
|
||||
|
||||
|
||||
@router.get('/{method_id}', response_model=PaymentMethodConfigResponse)
|
||||
async def get_payment_method(
|
||||
method_id: str,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""Get a single payment method configuration."""
|
||||
config = await get_config_by_method_id(db, method_id)
|
||||
if not config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f'Payment method not found: {method_id}',
|
||||
)
|
||||
defaults = _get_method_defaults()
|
||||
return _enrich_config(config, defaults)
|
||||
|
||||
|
||||
@router.put('/order')
|
||||
async def update_payment_methods_order(
|
||||
request: SortOrderRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""Batch update sort order for payment methods."""
|
||||
await update_sort_order(db, request.method_ids)
|
||||
logger.info(f'Admin {admin.id} updated payment methods order: {request.method_ids}')
|
||||
return {'success': True}
|
||||
|
||||
|
||||
@router.put('/{method_id}', response_model=PaymentMethodConfigResponse)
|
||||
async def update_payment_method(
|
||||
method_id: str,
|
||||
request: PaymentMethodConfigUpdateRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""Update a payment method configuration."""
|
||||
# Build update data dict
|
||||
data = {}
|
||||
|
||||
if request.is_enabled is not None:
|
||||
data['is_enabled'] = request.is_enabled
|
||||
|
||||
if request.reset_display_name:
|
||||
data['display_name'] = None
|
||||
elif request.display_name is not None:
|
||||
data['display_name'] = request.display_name.strip() or None
|
||||
|
||||
if request.sub_options is not None:
|
||||
data['sub_options'] = request.sub_options
|
||||
|
||||
if request.reset_min_amount:
|
||||
data['min_amount_kopeks'] = None
|
||||
elif request.min_amount_kopeks is not None:
|
||||
data['min_amount_kopeks'] = request.min_amount_kopeks
|
||||
|
||||
if request.reset_max_amount:
|
||||
data['max_amount_kopeks'] = None
|
||||
elif request.max_amount_kopeks is not None:
|
||||
data['max_amount_kopeks'] = request.max_amount_kopeks
|
||||
|
||||
if request.user_type_filter is not None:
|
||||
data['user_type_filter'] = request.user_type_filter
|
||||
|
||||
if request.first_topup_filter is not None:
|
||||
data['first_topup_filter'] = request.first_topup_filter
|
||||
|
||||
if request.promo_group_filter_mode is not None:
|
||||
data['promo_group_filter_mode'] = request.promo_group_filter_mode
|
||||
|
||||
promo_group_ids = request.allowed_promo_group_ids
|
||||
|
||||
config = await update_config(db, method_id, data, promo_group_ids)
|
||||
if not config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f'Payment method not found: {method_id}',
|
||||
)
|
||||
|
||||
logger.info(f'Admin {admin.id} updated payment method config: {method_id}')
|
||||
|
||||
defaults = _get_method_defaults()
|
||||
return _enrich_config(config, defaults)
|
||||
@@ -127,179 +127,149 @@ async def get_transactions(
|
||||
)
|
||||
|
||||
|
||||
@router.get('/payment-methods', response_model=list[PaymentMethodResponse])
|
||||
async def get_payment_methods():
|
||||
"""Get available payment methods."""
|
||||
methods = []
|
||||
async def _get_available_payment_methods(
|
||||
db: AsyncSession,
|
||||
user: User,
|
||||
) -> list[PaymentMethodResponse]:
|
||||
"""Get available payment methods filtered by DB config and user context.
|
||||
|
||||
# YooKassa - with card and SBP options
|
||||
if settings.is_yookassa_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='yookassa',
|
||||
name=settings.get_yookassa_display_name(),
|
||||
description='Pay via YooKassa',
|
||||
min_amount_kopeks=settings.YOOKASSA_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.YOOKASSA_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
options=[
|
||||
{'id': 'card', 'name': '💳 Карта', 'description': 'Банковская карта'},
|
||||
{'id': 'sbp', 'name': '🏦 СБП', 'description': 'Система быстрых платежей (QR)'},
|
||||
],
|
||||
)
|
||||
)
|
||||
Combines env-var availability with DB-based admin config (ordering, display conditions).
|
||||
"""
|
||||
from app.services.payment_method_config_service import (
|
||||
_get_method_defaults,
|
||||
get_all_configs,
|
||||
)
|
||||
|
||||
# CryptoBot
|
||||
if settings.is_cryptobot_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='cryptobot',
|
||||
name=settings.get_cryptobot_display_name(),
|
||||
description='Pay with cryptocurrency via CryptoBot',
|
||||
min_amount_kopeks=1000,
|
||||
max_amount_kopeks=10000000,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
configs = await get_all_configs(db)
|
||||
defaults = _get_method_defaults()
|
||||
|
||||
# Telegram Stars
|
||||
if settings.TELEGRAM_STARS_ENABLED:
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='telegram_stars',
|
||||
name=settings.get_telegram_stars_display_name(),
|
||||
description='Pay with Telegram Stars',
|
||||
min_amount_kopeks=100,
|
||||
max_amount_kopeks=1000000,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
# Provider availability checks from env vars
|
||||
provider_enabled = {
|
||||
'telegram_stars': settings.TELEGRAM_STARS_ENABLED,
|
||||
'tribute': settings.TRIBUTE_ENABLED and bool(getattr(settings, 'TRIBUTE_DONATE_LINK', '')),
|
||||
'cryptobot': settings.is_cryptobot_enabled(),
|
||||
'heleket': settings.is_heleket_enabled(),
|
||||
'yookassa': settings.is_yookassa_enabled(),
|
||||
'mulenpay': settings.is_mulenpay_enabled(),
|
||||
'pal24': settings.is_pal24_enabled(),
|
||||
'platega': settings.is_platega_enabled(),
|
||||
'wata': settings.is_wata_enabled(),
|
||||
'freekassa': settings.is_freekassa_enabled(),
|
||||
'cloudpayments': settings.is_cloudpayments_enabled(),
|
||||
}
|
||||
|
||||
# Heleket
|
||||
if settings.is_heleket_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='heleket',
|
||||
name=settings.get_heleket_display_name(),
|
||||
description='Pay with cryptocurrency via Heleket',
|
||||
min_amount_kopeks=1000,
|
||||
max_amount_kopeks=10000000,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
|
||||
# MulenPay
|
||||
if settings.is_mulenpay_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='mulenpay',
|
||||
name=settings.get_mulenpay_display_name(),
|
||||
description='MulenPay payment',
|
||||
min_amount_kopeks=settings.MULENPAY_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.MULENPAY_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
|
||||
# PAL24 - add options for card/sbp
|
||||
if settings.is_pal24_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='pal24',
|
||||
name=settings.get_pal24_display_name(),
|
||||
description='Pay via PAL24',
|
||||
min_amount_kopeks=settings.PAL24_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.PAL24_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
options=[
|
||||
{'id': 'sbp', 'name': '🏦 СБП', 'description': 'Система быстрых платежей'},
|
||||
{'id': 'card', 'name': '💳 Карта', 'description': 'Банковская карта'},
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# Platega - add options for different payment methods
|
||||
if settings.is_platega_enabled():
|
||||
platega_methods = settings.get_platega_active_methods()
|
||||
definitions = settings.get_platega_method_definitions()
|
||||
platega_options = []
|
||||
for method_code in platega_methods:
|
||||
info = definitions.get(method_code, {})
|
||||
platega_options.append(
|
||||
{
|
||||
# Default options builder (for methods with sub-options)
|
||||
def _build_options(method_id: str, config_sub_options: dict | None) -> list[dict] | None:
|
||||
if method_id == 'yookassa':
|
||||
all_opts = [
|
||||
{'id': 'card', 'name': '💳 Карта', 'description': 'Банковская карта'},
|
||||
{'id': 'sbp', 'name': '🏦 СБП', 'description': 'Система быстрых платежей (QR)'},
|
||||
]
|
||||
elif method_id == 'pal24':
|
||||
all_opts = [
|
||||
{'id': 'sbp', 'name': '🏦 СБП', 'description': 'Система быстрых платежей'},
|
||||
{'id': 'card', 'name': '💳 Карта', 'description': 'Банковская карта'},
|
||||
]
|
||||
elif method_id == 'platega':
|
||||
platega_methods = settings.get_platega_active_methods()
|
||||
definitions = settings.get_platega_method_definitions()
|
||||
all_opts = []
|
||||
for method_code in platega_methods:
|
||||
info = definitions.get(method_code, {})
|
||||
all_opts.append({
|
||||
'id': str(method_code),
|
||||
'name': info.get('title') or info.get('name') or f'Platega {method_code}',
|
||||
'description': info.get('description') or info.get('name') or '',
|
||||
}
|
||||
)
|
||||
})
|
||||
elif method_id == 'freekassa':
|
||||
all_opts = [
|
||||
{'id': 'sbp', 'name': '🏦 NSPK СБП', 'description': 'Система быстрых платежей'},
|
||||
{'id': 'card', 'name': '💳 Карта', 'description': 'Банковская карта'},
|
||||
]
|
||||
elif method_id == 'cloudpayments':
|
||||
all_opts = [
|
||||
{'id': 'card', 'name': '💳 Карта', 'description': 'Банковская карта'},
|
||||
{'id': 'sbp', 'name': '🏦 СБП', 'description': 'Система быстрых платежей'},
|
||||
]
|
||||
else:
|
||||
return None
|
||||
|
||||
if not all_opts:
|
||||
return None
|
||||
|
||||
# Filter by sub_options config from DB
|
||||
if config_sub_options:
|
||||
all_opts = [o for o in all_opts if config_sub_options.get(o['id'], True)]
|
||||
|
||||
return all_opts if all_opts else None
|
||||
|
||||
# User promo group IDs for filtering
|
||||
user_promo_group_ids: set[int] = set()
|
||||
if hasattr(user, 'user_promo_groups') and user.user_promo_groups:
|
||||
for upg in user.user_promo_groups:
|
||||
user_promo_group_ids.add(upg.promo_group_id)
|
||||
if hasattr(user, 'promo_group_id') and user.promo_group_id:
|
||||
user_promo_group_ids.add(user.promo_group_id)
|
||||
|
||||
methods = []
|
||||
for config in configs:
|
||||
mid = config.method_id
|
||||
|
||||
# 1. Check env-var provider availability AND DB admin toggle
|
||||
if not provider_enabled.get(mid, False):
|
||||
continue
|
||||
if not config.is_enabled:
|
||||
continue
|
||||
|
||||
# 2. Check user type filter
|
||||
if config.user_type_filter == 'telegram' and user.auth_type != 'telegram':
|
||||
continue
|
||||
if config.user_type_filter == 'email' and user.auth_type != 'email':
|
||||
continue
|
||||
|
||||
# 3. Check first topup filter
|
||||
if config.first_topup_filter == 'yes' and not user.has_made_first_topup:
|
||||
continue
|
||||
if config.first_topup_filter == 'no' and user.has_made_first_topup:
|
||||
continue
|
||||
|
||||
# 4. Check promo group filter
|
||||
if config.promo_group_filter_mode == 'selected' and config.allowed_promo_groups:
|
||||
allowed_ids = {pg.id for pg in config.allowed_promo_groups}
|
||||
if not user_promo_group_ids.intersection(allowed_ids):
|
||||
continue
|
||||
|
||||
# Build the response
|
||||
method_def = defaults.get(mid, {})
|
||||
display_name = config.display_name or method_def.get('default_display_name', mid)
|
||||
min_amount = config.min_amount_kopeks or method_def.get('default_min', 1000)
|
||||
max_amount = config.max_amount_kopeks or method_def.get('default_max', 10000000)
|
||||
options = _build_options(mid, config.sub_options)
|
||||
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='platega',
|
||||
name=settings.get_platega_display_name(),
|
||||
description='Pay via Platega',
|
||||
min_amount_kopeks=settings.PLATEGA_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.PLATEGA_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
options=platega_options if platega_options else None,
|
||||
)
|
||||
)
|
||||
|
||||
# Wata
|
||||
if settings.is_wata_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='wata',
|
||||
name=settings.get_wata_display_name(),
|
||||
description='Pay via Wata',
|
||||
min_amount_kopeks=settings.WATA_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.WATA_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
|
||||
# CloudPayments
|
||||
if settings.is_cloudpayments_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='cloudpayments',
|
||||
name=settings.get_cloudpayments_display_name(),
|
||||
description='Pay with bank card via CloudPayments',
|
||||
min_amount_kopeks=settings.CLOUDPAYMENTS_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.CLOUDPAYMENTS_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
|
||||
# FreeKassa
|
||||
if settings.is_freekassa_enabled():
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='freekassa',
|
||||
name=settings.get_freekassa_display_name(),
|
||||
description='Pay via FreeKassa',
|
||||
min_amount_kopeks=settings.FREEKASSA_MIN_AMOUNT_KOPEKS,
|
||||
max_amount_kopeks=settings.FREEKASSA_MAX_AMOUNT_KOPEKS,
|
||||
is_available=True,
|
||||
)
|
||||
)
|
||||
|
||||
# Tribute
|
||||
if settings.TRIBUTE_ENABLED and settings.TRIBUTE_DONATE_LINK:
|
||||
methods.append(
|
||||
PaymentMethodResponse(
|
||||
id='tribute',
|
||||
name='Tribute',
|
||||
description='Pay with bank card via Tribute',
|
||||
min_amount_kopeks=10000,
|
||||
max_amount_kopeks=10000000,
|
||||
id=mid,
|
||||
name=display_name,
|
||||
description=None,
|
||||
min_amount_kopeks=min_amount,
|
||||
max_amount_kopeks=max_amount,
|
||||
is_available=True,
|
||||
options=options,
|
||||
)
|
||||
)
|
||||
|
||||
return methods
|
||||
|
||||
|
||||
@router.get('/payment-methods', response_model=list[PaymentMethodResponse])
|
||||
async def get_payment_methods(
|
||||
user: User = Depends(get_current_cabinet_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""Get available payment methods."""
|
||||
return await _get_available_payment_methods(db, user)
|
||||
|
||||
|
||||
@router.post('/stars-invoice', response_model=StarsInvoiceResponse)
|
||||
async def create_stars_invoice(
|
||||
request: StarsInvoiceRequest,
|
||||
@@ -401,7 +371,7 @@ async def create_topup(
|
||||
):
|
||||
"""Create payment for balance top-up."""
|
||||
# Validate payment method
|
||||
methods = await get_payment_methods()
|
||||
methods = await _get_available_payment_methods(db, user)
|
||||
method = next((m for m in methods if m.id == request.payment_method), None)
|
||||
|
||||
if not method or not method.is_available:
|
||||
|
||||
Reference in New Issue
Block a user