fix: AuraPay webhook signature + add SBP/Card payment method selection
- Fix webhook signature: str(None) produced "None" (4 chars) instead of
"" like PHP implode() does, causing all webhooks with custom_fields=null
to fail signature verification
- Add AURAPAY_SBP_ENABLED / AURAPAY_CARD_ENABLED env vars with display
names, following Freekassa pattern for sub-method selection
- Add aurapay_sbp / aurapay_card buttons in payment keyboard
- Add start_aurapay_sbp_topup / start_aurapay_card_topup handlers
- Route dispatch handles aurapay / aurapay_sbp / aurapay_card
- payment_utils updated with SBP/Card availability checks
- service parameter ("sbp"/"card") now passed through to AuraPay API
This commit is contained in:
@@ -681,6 +681,10 @@ class Settings(BaseSettings):
|
||||
AURAPAY_WEBHOOK_PATH: str = '/aurapay-webhook'
|
||||
AURAPAY_RETURN_URL: str | None = None
|
||||
AURAPAY_PAYMENT_LIFETIME_MINUTES: int = 60
|
||||
AURAPAY_SBP_ENABLED: bool = False
|
||||
AURAPAY_SBP_DISPLAY_NAME: str = 'СБП (AuraPay)'
|
||||
AURAPAY_CARD_ENABLED: bool = False
|
||||
AURAPAY_CARD_DISPLAY_NAME: str = 'Карта (AuraPay)'
|
||||
|
||||
MAIN_MENU_MODE: str = 'default' # 'default' | 'cabinet'
|
||||
# Стиль кнопок Cabinet: primary (синий), success (зелёный), danger (красный), '' (по умолчанию для каждой секции)
|
||||
@@ -2118,6 +2122,26 @@ class Settings(BaseSettings):
|
||||
def get_aurapay_display_name_html(self) -> str:
|
||||
return html.escape(self.get_aurapay_display_name())
|
||||
|
||||
def is_aurapay_sbp_enabled(self) -> bool:
|
||||
return self.AURAPAY_SBP_ENABLED and self.is_aurapay_enabled()
|
||||
|
||||
def get_aurapay_sbp_display_name(self) -> str:
|
||||
name = (self.AURAPAY_SBP_DISPLAY_NAME or '').strip()
|
||||
return name or 'СБП (AuraPay)'
|
||||
|
||||
def get_aurapay_sbp_display_name_html(self) -> str:
|
||||
return html.escape(self.get_aurapay_sbp_display_name())
|
||||
|
||||
def is_aurapay_card_enabled(self) -> bool:
|
||||
return self.AURAPAY_CARD_ENABLED and self.is_aurapay_enabled()
|
||||
|
||||
def get_aurapay_card_display_name(self) -> str:
|
||||
name = (self.AURAPAY_CARD_DISPLAY_NAME or '').strip()
|
||||
return name or 'Карта (AuraPay)'
|
||||
|
||||
def get_aurapay_card_display_name_html(self) -> str:
|
||||
return html.escape(self.get_aurapay_card_display_name())
|
||||
|
||||
def is_kassa_ai_sbp_enabled(self) -> bool:
|
||||
return self.KASSA_AI_SBP_ENABLED and self.is_kassa_ai_enabled()
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ async def _create_aurapay_payment_and_respond(
|
||||
db: AsyncSession,
|
||||
amount_kopeks: int,
|
||||
edit_message: bool = False,
|
||||
payment_method_type: str | None = None,
|
||||
):
|
||||
"""
|
||||
Common logic for creating AuraPay payment and sending response.
|
||||
@@ -61,6 +62,7 @@ async def _create_aurapay_payment_and_respond(
|
||||
description=description,
|
||||
email=getattr(db_user, 'email', None),
|
||||
language=db_user.language,
|
||||
payment_method_type=payment_method_type,
|
||||
)
|
||||
|
||||
if not result:
|
||||
@@ -179,6 +181,11 @@ async def process_aurapay_payment_amount(
|
||||
)
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
payment_method = data.get('payment_method', 'aurapay')
|
||||
# aurapay_sbp → 'sbp', aurapay_card → 'card', aurapay → None
|
||||
payment_method_type = _extract_service_type(payment_method)
|
||||
|
||||
await state.clear()
|
||||
|
||||
await _create_aurapay_payment_and_respond(
|
||||
@@ -187,19 +194,30 @@ async def process_aurapay_payment_amount(
|
||||
db=db,
|
||||
amount_kopeks=amount_kopeks,
|
||||
edit_message=False,
|
||||
payment_method_type=payment_method_type,
|
||||
)
|
||||
|
||||
|
||||
@error_handler
|
||||
async def start_aurapay_topup(
|
||||
AURAPAY_PAYMENT_METHODS = {'aurapay', 'aurapay_sbp', 'aurapay_card'}
|
||||
|
||||
AURAPAY_SERVICE_MAP: dict[str, str | None] = {
|
||||
'aurapay': None,
|
||||
'aurapay_sbp': 'sbp',
|
||||
'aurapay_card': 'card',
|
||||
}
|
||||
|
||||
|
||||
def _extract_service_type(payment_method: str) -> str | None:
|
||||
return AURAPAY_SERVICE_MAP.get(payment_method)
|
||||
|
||||
|
||||
async def _start_aurapay_topup_impl(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
payment_method: str,
|
||||
):
|
||||
"""
|
||||
Start AuraPay top-up process - ask for amount.
|
||||
"""
|
||||
"""Common logic for starting AuraPay top-up (generic / SBP / card)."""
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
restriction_kb = _check_topup_restriction(db_user, texts)
|
||||
@@ -213,11 +231,18 @@ async def start_aurapay_topup(
|
||||
return
|
||||
|
||||
await state.set_state(BalanceStates.waiting_for_amount)
|
||||
await state.update_data(payment_method='aurapay')
|
||||
await state.update_data(payment_method=payment_method)
|
||||
|
||||
min_amount = settings.AURAPAY_MIN_AMOUNT_KOPEKS // 100
|
||||
max_amount = settings.AURAPAY_MAX_AMOUNT_KOPEKS // 100
|
||||
display_name = settings.get_aurapay_display_name()
|
||||
|
||||
# Choose display name based on sub-method
|
||||
if payment_method == 'aurapay_sbp':
|
||||
display_name = settings.get_aurapay_sbp_display_name()
|
||||
elif payment_method == 'aurapay_card':
|
||||
display_name = settings.get_aurapay_card_display_name()
|
||||
else:
|
||||
display_name = settings.get_aurapay_display_name()
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
@@ -245,3 +270,33 @@ async def start_aurapay_topup(
|
||||
parse_mode='HTML',
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
|
||||
@error_handler
|
||||
async def start_aurapay_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
await _start_aurapay_topup_impl(callback, db_user, state, 'aurapay')
|
||||
|
||||
|
||||
@error_handler
|
||||
async def start_aurapay_sbp_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
await _start_aurapay_topup_impl(callback, db_user, state, 'aurapay_sbp')
|
||||
|
||||
|
||||
@error_handler
|
||||
async def start_aurapay_card_topup(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
):
|
||||
await _start_aurapay_topup_impl(callback, db_user, state, 'aurapay_card')
|
||||
|
||||
@@ -170,7 +170,7 @@ async def route_payment_by_method(
|
||||
await process_overpay_payment_amount(message, db_user, db, amount_kopeks, state)
|
||||
return True
|
||||
|
||||
if payment_method == 'aurapay':
|
||||
if payment_method in ('aurapay', 'aurapay_sbp', 'aurapay_card'):
|
||||
from .aurapay import process_aurapay_payment_amount
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
@@ -768,9 +768,11 @@ def register_balance_handlers(dp: Dispatcher):
|
||||
|
||||
dp.callback_query.register(start_overpay_topup, F.data == 'topup_overpay')
|
||||
|
||||
from .aurapay import start_aurapay_topup
|
||||
from .aurapay import start_aurapay_card_topup, start_aurapay_sbp_topup, start_aurapay_topup
|
||||
|
||||
dp.callback_query.register(start_aurapay_topup, F.data == 'topup_aurapay')
|
||||
dp.callback_query.register(start_aurapay_sbp_topup, F.data == 'topup_aurapay_sbp')
|
||||
dp.callback_query.register(start_aurapay_card_topup, F.data == 'topup_aurapay_card')
|
||||
|
||||
from .mulenpay import check_mulenpay_payment_status
|
||||
|
||||
|
||||
+29
-1
@@ -1850,7 +1850,35 @@ def get_payment_methods_keyboard(amount_kopeks: int, language: str = DEFAULT_LAN
|
||||
)
|
||||
has_direct_payment_methods = True
|
||||
|
||||
if settings.is_aurapay_enabled():
|
||||
if settings.is_aurapay_sbp_enabled():
|
||||
sbp_name = settings.get_aurapay_sbp_display_name()
|
||||
keyboard.append(
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=texts.t('PAYMENT_AURAPAY_SBP', f'📱 {sbp_name}'),
|
||||
callback_data=_build_callback('aurapay_sbp'),
|
||||
)
|
||||
]
|
||||
)
|
||||
has_direct_payment_methods = True
|
||||
|
||||
if settings.is_aurapay_card_enabled():
|
||||
card_name = settings.get_aurapay_card_display_name()
|
||||
keyboard.append(
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=texts.t('PAYMENT_AURAPAY_CARD', f'💳 {card_name}'),
|
||||
callback_data=_build_callback('aurapay_card'),
|
||||
)
|
||||
]
|
||||
)
|
||||
has_direct_payment_methods = True
|
||||
|
||||
if (
|
||||
settings.is_aurapay_enabled()
|
||||
and not settings.is_aurapay_sbp_enabled()
|
||||
and not settings.is_aurapay_card_enabled()
|
||||
):
|
||||
aurapay_name = settings.get_aurapay_display_name()
|
||||
keyboard.append(
|
||||
[
|
||||
|
||||
@@ -197,8 +197,11 @@ class AuraPayService:
|
||||
return False
|
||||
|
||||
# Сортируем ключи по алфавиту и конкатенируем значения
|
||||
# None → '' (PHP implode() converts null to empty string, not "None")
|
||||
sorted_keys = sorted(payload.keys())
|
||||
concatenated_values = ''.join(str(payload[key]) for key in sorted_keys)
|
||||
concatenated_values = ''.join(
|
||||
str(payload[key]) if payload[key] is not None else '' for key in sorted_keys
|
||||
)
|
||||
|
||||
expected = hmac.new(
|
||||
self.secret_key.encode('utf-8'),
|
||||
|
||||
@@ -232,7 +232,35 @@ def get_available_payment_methods() -> list[dict[str, str]]:
|
||||
}
|
||||
)
|
||||
|
||||
if settings.is_aurapay_enabled():
|
||||
if settings.is_aurapay_sbp_enabled():
|
||||
sbp_name = settings.get_aurapay_sbp_display_name()
|
||||
methods.append(
|
||||
{
|
||||
'id': 'aurapay_sbp',
|
||||
'name': sbp_name,
|
||||
'icon': '📱',
|
||||
'description': f'через {sbp_name}',
|
||||
'callback': 'topup_aurapay_sbp',
|
||||
}
|
||||
)
|
||||
|
||||
if settings.is_aurapay_card_enabled():
|
||||
card_name = settings.get_aurapay_card_display_name()
|
||||
methods.append(
|
||||
{
|
||||
'id': 'aurapay_card',
|
||||
'name': card_name,
|
||||
'icon': '💳',
|
||||
'description': f'через {card_name}',
|
||||
'callback': 'topup_aurapay_card',
|
||||
}
|
||||
)
|
||||
|
||||
if (
|
||||
settings.is_aurapay_enabled()
|
||||
and not settings.is_aurapay_sbp_enabled()
|
||||
and not settings.is_aurapay_card_enabled()
|
||||
):
|
||||
aurapay_name = settings.get_aurapay_display_name()
|
||||
methods.append(
|
||||
{
|
||||
@@ -381,6 +409,10 @@ def is_payment_method_available(method_id: str) -> bool:
|
||||
return settings.is_overpay_enabled()
|
||||
if method_id == 'aurapay':
|
||||
return settings.is_aurapay_enabled()
|
||||
if method_id == 'aurapay_sbp':
|
||||
return settings.is_aurapay_sbp_enabled()
|
||||
if method_id == 'aurapay_card':
|
||||
return settings.is_aurapay_card_enabled()
|
||||
if method_id == 'support':
|
||||
return settings.is_support_topup_enabled()
|
||||
return False
|
||||
@@ -409,6 +441,8 @@ def get_payment_method_status() -> dict[str, bool]:
|
||||
'rollypay': settings.is_rollypay_enabled(),
|
||||
'overpay': settings.is_overpay_enabled(),
|
||||
'aurapay': settings.is_aurapay_enabled(),
|
||||
'aurapay_sbp': settings.is_aurapay_sbp_enabled(),
|
||||
'aurapay_card': settings.is_aurapay_card_enabled(),
|
||||
'support': settings.is_support_topup_enabled(),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user