Merge pull request #2746 from smediainfo/pr/kassa-ai-sbp-card
feat: add SBP and Card sub-options for KassaAI payment method
This commit is contained in:
@@ -701,6 +701,11 @@ async def create_topup(
|
||||
detail='KassaAI payment method is unavailable',
|
||||
)
|
||||
|
||||
# Use payment_option to select sbp or card
|
||||
KASSA_AI_OPTION_MAP = {'sbp': 44, 'card': 36}
|
||||
option = (request.payment_option or '').strip().lower()
|
||||
ps_id = KASSA_AI_OPTION_MAP.get(option) # None = use env default
|
||||
|
||||
payment_service = PaymentService()
|
||||
result = await payment_service.create_kassa_ai_payment(
|
||||
db=db,
|
||||
@@ -709,6 +714,7 @@ async def create_topup(
|
||||
description=settings.get_balance_payment_description(request.amount_kopeks),
|
||||
email=getattr(user, 'email', None),
|
||||
language=getattr(user, 'language', None) or settings.DEFAULT_LANGUAGE,
|
||||
payment_system_id=ps_id,
|
||||
)
|
||||
|
||||
if result and result.get('payment_url'):
|
||||
|
||||
@@ -535,6 +535,11 @@ class Settings(BaseSettings):
|
||||
KASSA_AI_WEBHOOK_PORT: int = 8089
|
||||
# Способ оплаты: 44 = СБП (QR код), 36 = Карты РФ, 43 = SberPay
|
||||
KASSA_AI_PAYMENT_SYSTEM_ID: int = 44
|
||||
# Раздельные методы оплаты KassaAI (отображаются как отдельные кнопки)
|
||||
KASSA_AI_SBP_ENABLED: bool = False # СБП — payment_system_id=44
|
||||
KASSA_AI_SBP_DISPLAY_NAME: str = 'СБП (KassaAI)'
|
||||
KASSA_AI_CARD_ENABLED: bool = False # Карты РФ — payment_system_id=36
|
||||
KASSA_AI_CARD_DISPLAY_NAME: str = 'Карта (KassaAI)'
|
||||
|
||||
# RioPay (api.riopay.online) v2.0.1
|
||||
RIOPAY_ENABLED: bool = False
|
||||
@@ -1850,6 +1855,26 @@ class Settings(BaseSettings):
|
||||
def get_riopay_display_name_html(self) -> str:
|
||||
return html.escape(self.get_riopay_display_name())
|
||||
|
||||
def is_kassa_ai_sbp_enabled(self) -> bool:
|
||||
return self.KASSA_AI_SBP_ENABLED and self.is_kassa_ai_enabled()
|
||||
|
||||
def get_kassa_ai_sbp_display_name(self) -> str:
|
||||
name = (self.KASSA_AI_SBP_DISPLAY_NAME or '').strip()
|
||||
return name if name else 'СБП (KassaAI)'
|
||||
|
||||
def get_kassa_ai_sbp_display_name_html(self) -> str:
|
||||
return html.escape(self.get_kassa_ai_sbp_display_name())
|
||||
|
||||
def is_kassa_ai_card_enabled(self) -> bool:
|
||||
return self.KASSA_AI_CARD_ENABLED and self.is_kassa_ai_enabled()
|
||||
|
||||
def get_kassa_ai_card_display_name(self) -> str:
|
||||
name = (self.KASSA_AI_CARD_DISPLAY_NAME or '').strip()
|
||||
return name if name else 'Карта (KassaAI)'
|
||||
|
||||
def get_kassa_ai_card_display_name_html(self) -> str:
|
||||
return html.escape(self.get_kassa_ai_card_display_name())
|
||||
|
||||
def is_payment_verification_auto_check_enabled(self) -> bool:
|
||||
return self.PAYMENT_VERIFICATION_AUTO_CHECK_ENABLED
|
||||
|
||||
|
||||
+170
-107
@@ -10,6 +10,7 @@ from app.config import settings
|
||||
from app.database.models import User
|
||||
from app.keyboards.inline import get_back_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.services.kassa_ai_service import KASSA_AI_SUB_METHODS
|
||||
from app.services.payment_service import PaymentService
|
||||
from app.states import BalanceStates
|
||||
from app.utils.decorators import error_handler
|
||||
@@ -18,23 +19,55 @@ from app.utils.decorators import error_handler
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
# --- Enabled check + display name lookup by payment method ---
|
||||
|
||||
_KASSA_AI_METHOD_CONFIG = {
|
||||
'kassa_ai': {
|
||||
'is_enabled': settings.is_kassa_ai_enabled,
|
||||
'display_name': settings.get_kassa_ai_display_name,
|
||||
'unavailable_text': 'KassaAI временно недоступен',
|
||||
},
|
||||
'kassa_ai_sbp': {
|
||||
'is_enabled': settings.is_kassa_ai_sbp_enabled,
|
||||
'display_name': settings.get_kassa_ai_sbp_display_name,
|
||||
'unavailable_text': 'KassaAI СБП временно недоступен',
|
||||
},
|
||||
'kassa_ai_card': {
|
||||
'is_enabled': settings.is_kassa_ai_card_enabled,
|
||||
'display_name': settings.get_kassa_ai_card_display_name,
|
||||
'unavailable_text': 'KassaAI Карта временно недоступна',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def _check_topup_restriction(callback: types.CallbackQuery, db_user: User) -> bool:
|
||||
"""Check if user has topup restriction. Returns True if restricted (handler should abort)."""
|
||||
if not getattr(db_user, 'restriction_topup', False):
|
||||
return False
|
||||
texts = get_texts(db_user.language)
|
||||
reason = getattr(db_user, 'restriction_reason', None) or 'Действие ограничено администратором'
|
||||
support_url = settings.get_support_contact_url()
|
||||
keyboard = []
|
||||
if support_url:
|
||||
keyboard.append([InlineKeyboardButton(text='🆘 Обжаловать', url=support_url)])
|
||||
keyboard.append([InlineKeyboardButton(text=texts.BACK, callback_data='menu_balance')])
|
||||
await callback.message.edit_text(
|
||||
f'🚫 <b>Пополнение ограничено</b>\n\n{reason}',
|
||||
parse_mode='HTML',
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def _create_kassa_ai_payment_and_respond(
|
||||
message_or_callback,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
amount_kopeks: int,
|
||||
edit_message: bool = False,
|
||||
payment_method: str = 'kassa_ai',
|
||||
):
|
||||
"""
|
||||
Common logic for creating KassaAI payment and sending response.
|
||||
|
||||
Args:
|
||||
message_or_callback: Either a Message or CallbackQuery object
|
||||
db_user: User object
|
||||
db: Database session
|
||||
amount_kopeks: Amount in kopeks
|
||||
edit_message: Whether to edit existing message or send new one
|
||||
"""
|
||||
"""Common logic for creating KassaAI payment and sending response."""
|
||||
texts = get_texts(db_user.language)
|
||||
amount_rub = amount_kopeks / 100
|
||||
|
||||
@@ -46,6 +79,9 @@ async def _create_kassa_ai_payment_and_respond(
|
||||
description='Пополнение баланса',
|
||||
)
|
||||
|
||||
sub = KASSA_AI_SUB_METHODS.get(payment_method)
|
||||
payment_system_id = sub['payment_system_id'] if sub else settings.KASSA_AI_PAYMENT_SYSTEM_ID
|
||||
|
||||
result = await payment_service.create_kassa_ai_payment(
|
||||
db=db,
|
||||
user_id=db_user.id,
|
||||
@@ -53,6 +89,7 @@ async def _create_kassa_ai_payment_and_respond(
|
||||
description=description,
|
||||
email=getattr(db_user, 'email', None),
|
||||
language=db_user.language,
|
||||
payment_system_id=payment_system_id,
|
||||
)
|
||||
|
||||
if not result:
|
||||
@@ -74,7 +111,8 @@ async def _create_kassa_ai_payment_and_respond(
|
||||
return
|
||||
|
||||
payment_url = result.get('payment_url')
|
||||
display_name = settings.get_kassa_ai_display_name()
|
||||
cfg = _KASSA_AI_METHOD_CONFIG.get(payment_method, _KASSA_AI_METHOD_CONFIG['kassa_ai'])
|
||||
display_name = cfg['display_name']()
|
||||
|
||||
# Create keyboard with payment button
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
@@ -128,10 +166,9 @@ async def process_kassa_ai_payment_amount(
|
||||
db: AsyncSession,
|
||||
amount_kopeks: int,
|
||||
state: FSMContext,
|
||||
payment_method: str = 'kassa_ai',
|
||||
):
|
||||
"""
|
||||
Process payment amount directly (called from quick_amount handlers).
|
||||
"""
|
||||
"""Process payment amount directly (called from custom_amount and quick_amount handlers)."""
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
# Проверка ограничения на пополнение
|
||||
@@ -183,53 +220,40 @@ async def process_kassa_ai_payment_amount(
|
||||
db=db,
|
||||
amount_kopeks=amount_kopeks,
|
||||
edit_message=False,
|
||||
payment_method=payment_method,
|
||||
)
|
||||
|
||||
|
||||
@error_handler
|
||||
async def start_kassa_ai_topup(
|
||||
# --- Generic start/quick-amount implementations ---
|
||||
|
||||
|
||||
async def _start_kassa_ai_sub_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
payment_method: str,
|
||||
):
|
||||
"""
|
||||
Start KassaAI top-up process - ask for amount.
|
||||
"""
|
||||
"""Generic start topup handler for any KassaAI sub-method."""
|
||||
cfg = _KASSA_AI_METHOD_CONFIG[payment_method]
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
# Проверка ограничения на пополнение
|
||||
if getattr(db_user, 'restriction_topup', False):
|
||||
reason = getattr(db_user, 'restriction_reason', None) or 'Действие ограничено администратором'
|
||||
support_url = settings.get_support_contact_url()
|
||||
keyboard = []
|
||||
if support_url:
|
||||
keyboard.append([InlineKeyboardButton(text='🆘 Обжаловать', url=support_url)])
|
||||
keyboard.append([InlineKeyboardButton(text=texts.BACK, callback_data='menu_balance')])
|
||||
if not cfg['is_enabled']():
|
||||
await callback.answer(texts.t('KASSA_AI_NOT_AVAILABLE', cfg['unavailable_text']), show_alert=True)
|
||||
return
|
||||
|
||||
await callback.message.edit_text(
|
||||
f'🚫 <b>Пополнение ограничено</b>\n\n{reason}',
|
||||
parse_mode='HTML',
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||
)
|
||||
if await _check_topup_restriction(callback, db_user):
|
||||
return
|
||||
|
||||
await state.set_state(BalanceStates.waiting_for_amount)
|
||||
await state.update_data(payment_method='kassa_ai')
|
||||
await state.update_data(payment_method=payment_method)
|
||||
|
||||
min_amount = settings.KASSA_AI_MIN_AMOUNT_KOPEKS // 100
|
||||
max_amount = settings.KASSA_AI_MAX_AMOUNT_KOPEKS // 100
|
||||
display_name = settings.get_kassa_ai_display_name()
|
||||
display_name = cfg['display_name']()
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=texts.t('BACK_BUTTON', '◀️ Назад'),
|
||||
callback_data='menu_balance',
|
||||
)
|
||||
]
|
||||
]
|
||||
inline_keyboard=[[InlineKeyboardButton(text=texts.t('BACK_BUTTON', '◀️ Назад'), callback_data='menu_balance')]]
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
@@ -249,6 +273,68 @@ async def start_kassa_ai_topup(
|
||||
)
|
||||
|
||||
|
||||
async def _process_kassa_ai_sub_quick_amount(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
payment_method: str,
|
||||
):
|
||||
"""Generic quick amount handler for any KassaAI sub-method."""
|
||||
cfg = _KASSA_AI_METHOD_CONFIG[payment_method]
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
if not cfg['is_enabled']():
|
||||
await callback.answer(texts.t('KASSA_AI_NOT_AVAILABLE', cfg['unavailable_text']), show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
parts = callback.data.split('|')
|
||||
amount_kopeks = int(parts[2]) if len(parts) >= 3 else None
|
||||
if amount_kopeks is None:
|
||||
raise ValueError
|
||||
except (ValueError, IndexError):
|
||||
await callback.answer('Invalid amount', show_alert=True)
|
||||
return
|
||||
|
||||
if await _check_topup_restriction(callback, db_user):
|
||||
return
|
||||
|
||||
min_amount = settings.KASSA_AI_MIN_AMOUNT_KOPEKS
|
||||
max_amount = settings.KASSA_AI_MAX_AMOUNT_KOPEKS
|
||||
if amount_kopeks < min_amount:
|
||||
await callback.answer(texts.t('AMOUNT_TOO_LOW_SHORT', 'Сумма слишком мала'), show_alert=True)
|
||||
return
|
||||
if amount_kopeks > max_amount:
|
||||
await callback.answer(texts.t('AMOUNT_TOO_HIGH_SHORT', 'Сумма слишком велика'), show_alert=True)
|
||||
return
|
||||
|
||||
await callback.answer()
|
||||
await state.clear()
|
||||
await _create_kassa_ai_payment_and_respond(
|
||||
message_or_callback=callback.message,
|
||||
db_user=db_user,
|
||||
db=db,
|
||||
amount_kopeks=amount_kopeks,
|
||||
edit_message=True,
|
||||
payment_method=payment_method,
|
||||
)
|
||||
|
||||
|
||||
# --- Public handler functions (registered in main.py) ---
|
||||
|
||||
|
||||
@error_handler
|
||||
async def start_kassa_ai_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""Start KassaAI top-up process - ask for amount."""
|
||||
await _start_kassa_ai_sub_topup(callback, db_user, db, state, 'kassa_ai')
|
||||
|
||||
|
||||
@error_handler
|
||||
async def process_kassa_ai_custom_amount(
|
||||
message: types.Message,
|
||||
@@ -256,11 +342,10 @@ async def process_kassa_ai_custom_amount(
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""
|
||||
Process custom amount input for KassaAI payment.
|
||||
"""
|
||||
"""Process custom amount input for KassaAI payment."""
|
||||
data = await state.get_data()
|
||||
if data.get('payment_method') != 'kassa_ai':
|
||||
pm = data.get('payment_method', 'kassa_ai')
|
||||
if pm not in _KASSA_AI_METHOD_CONFIG:
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
@@ -285,6 +370,7 @@ async def process_kassa_ai_custom_amount(
|
||||
db=db,
|
||||
amount_kopeks=amount_kopeks,
|
||||
state=state,
|
||||
payment_method=pm,
|
||||
)
|
||||
|
||||
|
||||
@@ -295,72 +381,49 @@ async def process_kassa_ai_quick_amount(
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""
|
||||
Process quick amount selection for KassaAI payment.
|
||||
Called when user clicks a predefined amount button.
|
||||
"""
|
||||
texts = get_texts(db_user.language)
|
||||
"""Process quick amount selection for KassaAI payment."""
|
||||
await _process_kassa_ai_sub_quick_amount(callback, db_user, db, state, 'kassa_ai')
|
||||
|
||||
if not settings.is_kassa_ai_enabled():
|
||||
await callback.answer(
|
||||
texts.t('KASSA_AI_NOT_AVAILABLE', 'KassaAI временно недоступен'),
|
||||
show_alert=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Extract amount from callback data: topup_amount|kassa_ai|{amount_kopeks}
|
||||
try:
|
||||
parts = callback.data.split('|')
|
||||
if len(parts) >= 3:
|
||||
amount_kopeks = int(parts[2])
|
||||
else:
|
||||
await callback.answer('Invalid callback data', show_alert=True)
|
||||
return
|
||||
except (ValueError, IndexError):
|
||||
await callback.answer('Invalid amount', show_alert=True)
|
||||
return
|
||||
@error_handler
|
||||
async def start_kassa_ai_sbp_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""Start KassaAI SBP top-up process."""
|
||||
await _start_kassa_ai_sub_topup(callback, db_user, db, state, 'kassa_ai_sbp')
|
||||
|
||||
# Проверка ограничения на пополнение
|
||||
if getattr(db_user, 'restriction_topup', False):
|
||||
reason = getattr(db_user, 'restriction_reason', None) or 'Действие ограничено администратором'
|
||||
support_url = settings.get_support_contact_url()
|
||||
keyboard = []
|
||||
if support_url:
|
||||
keyboard.append([InlineKeyboardButton(text='🆘 Обжаловать', url=support_url)])
|
||||
keyboard.append([InlineKeyboardButton(text=texts.BACK, callback_data='menu_balance')])
|
||||
|
||||
await callback.message.edit_text(
|
||||
f'🚫 <b>Пополнение ограничено</b>\n\n{reason}',
|
||||
parse_mode='HTML',
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||
)
|
||||
return
|
||||
@error_handler
|
||||
async def process_kassa_ai_sbp_quick_amount(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""Process quick amount for KassaAI SBP."""
|
||||
await _process_kassa_ai_sub_quick_amount(callback, db_user, db, state, 'kassa_ai_sbp')
|
||||
|
||||
# Validate amount
|
||||
min_amount = settings.KASSA_AI_MIN_AMOUNT_KOPEKS
|
||||
max_amount = settings.KASSA_AI_MAX_AMOUNT_KOPEKS
|
||||
|
||||
if amount_kopeks < min_amount:
|
||||
await callback.answer(
|
||||
texts.t('AMOUNT_TOO_LOW_SHORT', 'Сумма слишком мала'),
|
||||
show_alert=True,
|
||||
)
|
||||
return
|
||||
@error_handler
|
||||
async def start_kassa_ai_card_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""Start KassaAI Card top-up process."""
|
||||
await _start_kassa_ai_sub_topup(callback, db_user, db, state, 'kassa_ai_card')
|
||||
|
||||
if amount_kopeks > max_amount:
|
||||
await callback.answer(
|
||||
texts.t('AMOUNT_TOO_HIGH_SHORT', 'Сумма слишком велика'),
|
||||
show_alert=True,
|
||||
)
|
||||
return
|
||||
|
||||
await callback.answer()
|
||||
await state.clear()
|
||||
|
||||
await _create_kassa_ai_payment_and_respond(
|
||||
message_or_callback=callback.message,
|
||||
db_user=db_user,
|
||||
db=db,
|
||||
amount_kopeks=amount_kopeks,
|
||||
edit_message=True,
|
||||
)
|
||||
@error_handler
|
||||
async def process_kassa_ai_card_quick_amount(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
"""Process quick amount for KassaAI Card."""
|
||||
await _process_kassa_ai_sub_quick_amount(callback, db_user, db, state, 'kassa_ai_card')
|
||||
|
||||
@@ -131,11 +131,13 @@ async def route_payment_by_method(
|
||||
)
|
||||
return True
|
||||
|
||||
if payment_method == 'kassa_ai':
|
||||
if payment_method in ('kassa_ai', 'kassa_ai_sbp', 'kassa_ai_card'):
|
||||
from .kassa_ai import process_kassa_ai_payment_amount
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
await process_kassa_ai_payment_amount(message, db_user, db, amount_kopeks, state)
|
||||
await process_kassa_ai_payment_amount(
|
||||
message, db_user, db, amount_kopeks, state, payment_method=payment_method
|
||||
)
|
||||
return True
|
||||
|
||||
if payment_method == 'riopay':
|
||||
@@ -790,10 +792,21 @@ def register_balance_handlers(dp: Dispatcher):
|
||||
dp.callback_query.register(start_freekassa_card_topup, F.data == 'topup_freekassa_card')
|
||||
dp.callback_query.register(process_freekassa_card_quick_amount, F.data.startswith('topup_amount|freekassa_card|'))
|
||||
|
||||
from .kassa_ai import process_kassa_ai_quick_amount, start_kassa_ai_topup
|
||||
from .kassa_ai import (
|
||||
process_kassa_ai_card_quick_amount,
|
||||
process_kassa_ai_quick_amount,
|
||||
process_kassa_ai_sbp_quick_amount,
|
||||
start_kassa_ai_card_topup,
|
||||
start_kassa_ai_sbp_topup,
|
||||
start_kassa_ai_topup,
|
||||
)
|
||||
|
||||
dp.callback_query.register(start_kassa_ai_topup, F.data == 'topup_kassa_ai')
|
||||
dp.callback_query.register(process_kassa_ai_quick_amount, F.data.startswith('topup_amount|kassa_ai|'))
|
||||
dp.callback_query.register(start_kassa_ai_sbp_topup, F.data == 'topup_kassa_ai_sbp')
|
||||
dp.callback_query.register(process_kassa_ai_sbp_quick_amount, F.data.startswith('topup_amount|kassa_ai_sbp|'))
|
||||
dp.callback_query.register(start_kassa_ai_card_topup, F.data == 'topup_kassa_ai_card')
|
||||
dp.callback_query.register(process_kassa_ai_card_quick_amount, F.data.startswith('topup_amount|kassa_ai_card|'))
|
||||
|
||||
from .riopay import process_riopay_quick_amount, start_riopay_topup
|
||||
|
||||
|
||||
+29
-1
@@ -1695,7 +1695,35 @@ def get_payment_methods_keyboard(amount_kopeks: int, language: str = DEFAULT_LAN
|
||||
)
|
||||
has_direct_payment_methods = True
|
||||
|
||||
if settings.is_kassa_ai_enabled():
|
||||
if settings.is_kassa_ai_sbp_enabled():
|
||||
sbp_name = settings.get_kassa_ai_sbp_display_name()
|
||||
keyboard.append(
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=texts.t('PAYMENT_KASSA_AI_SBP', f'📱 {sbp_name}'),
|
||||
callback_data=_build_callback('kassa_ai_sbp'),
|
||||
)
|
||||
]
|
||||
)
|
||||
has_direct_payment_methods = True
|
||||
|
||||
if settings.is_kassa_ai_card_enabled():
|
||||
card_name = settings.get_kassa_ai_card_display_name()
|
||||
keyboard.append(
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=texts.t('PAYMENT_KASSA_AI_CARD', f'💳 {card_name}'),
|
||||
callback_data=_build_callback('kassa_ai_card'),
|
||||
)
|
||||
]
|
||||
)
|
||||
has_direct_payment_methods = True
|
||||
|
||||
if (
|
||||
settings.is_kassa_ai_enabled()
|
||||
and not settings.is_kassa_ai_sbp_enabled()
|
||||
and not settings.is_kassa_ai_card_enabled()
|
||||
):
|
||||
kassa_ai_name = settings.get_kassa_ai_display_name()
|
||||
keyboard.append(
|
||||
[
|
||||
|
||||
@@ -14,6 +14,12 @@ from app.config import settings
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
# Sub-method to payment_system_id mapping
|
||||
KASSA_AI_SUB_METHODS = {
|
||||
'kassa_ai_sbp': {'payment_system_id': 44},
|
||||
'kassa_ai_card': {'payment_system_id': 36},
|
||||
}
|
||||
|
||||
# Кэш для публичного IP
|
||||
_cached_public_ip: str | None = None
|
||||
_ip_fetch_lock = asyncio.Lock()
|
||||
|
||||
@@ -28,6 +28,7 @@ class KassaAiPaymentMixin:
|
||||
description: str = 'Пополнение баланса',
|
||||
email: str | None = None,
|
||||
language: str = 'ru',
|
||||
payment_system_id: int | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""
|
||||
Создает платеж KassaAI.
|
||||
@@ -96,7 +97,9 @@ class KassaAiPaymentMixin:
|
||||
amount=amount_rubles,
|
||||
currency=currency,
|
||||
email=email,
|
||||
payment_system_id=settings.KASSA_AI_PAYMENT_SYSTEM_ID,
|
||||
payment_system_id=payment_system_id
|
||||
if payment_system_id is not None
|
||||
else settings.KASSA_AI_PAYMENT_SYSTEM_ID,
|
||||
)
|
||||
|
||||
payment_url = result.get('location')
|
||||
@@ -118,7 +121,9 @@ class KassaAiPaymentMixin:
|
||||
currency=currency,
|
||||
description=description,
|
||||
payment_url=payment_url,
|
||||
payment_system_id=settings.KASSA_AI_PAYMENT_SYSTEM_ID,
|
||||
payment_system_id=payment_system_id
|
||||
if payment_system_id is not None
|
||||
else settings.KASSA_AI_PAYMENT_SYSTEM_ID,
|
||||
expires_at=expires_at,
|
||||
metadata_json=metadata,
|
||||
)
|
||||
|
||||
@@ -126,7 +126,10 @@ def _get_method_defaults() -> dict:
|
||||
'is_configured': settings.is_kassa_ai_enabled(),
|
||||
'default_min': settings.KASSA_AI_MIN_AMOUNT_KOPEKS,
|
||||
'default_max': settings.KASSA_AI_MAX_AMOUNT_KOPEKS,
|
||||
'available_sub_options': None,
|
||||
'available_sub_options': [
|
||||
{'id': 'sbp', 'name': 'СБП'},
|
||||
{'id': 'card', 'name': 'Карта'},
|
||||
],
|
||||
},
|
||||
'riopay': {
|
||||
'default_display_name': settings.get_riopay_display_name(),
|
||||
|
||||
@@ -666,23 +666,29 @@ class PaymentService(
|
||||
return None
|
||||
|
||||
# --- KassaAI ----------------------------------------------------------
|
||||
if payment_method == 'kassa_ai':
|
||||
if payment_method in ('kassa_ai', 'kassa_ai_sbp', 'kassa_ai_card'):
|
||||
if not settings.is_kassa_ai_enabled():
|
||||
logger.warning('KassaAI is not enabled, cannot create guest payment')
|
||||
return None
|
||||
|
||||
from app.services.kassa_ai_service import KASSA_AI_SUB_METHODS
|
||||
|
||||
sub = KASSA_AI_SUB_METHODS.get(payment_method)
|
||||
ps_id = sub['payment_system_id'] if sub else None
|
||||
|
||||
result = await self.create_kassa_ai_payment(
|
||||
db=db,
|
||||
user_id=None,
|
||||
amount_kopeks=amount_kopeks,
|
||||
description=description,
|
||||
payment_system_id=ps_id,
|
||||
)
|
||||
if result:
|
||||
await _patch_guest_metadata(result['local_payment_id'], 'kassa_ai')
|
||||
await _patch_guest_metadata(result['local_payment_id'], payment_method)
|
||||
return {
|
||||
'payment_url': result.get('payment_url'),
|
||||
'payment_id': result.get('order_id'),
|
||||
'provider': 'kassa_ai',
|
||||
'provider': payment_method,
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user