diff --git a/bot/handlers/user/payment.py b/bot/handlers/user/payment.py
index 2f7f473..361df09 100644
--- a/bot/handlers/user/payment.py
+++ b/bot/handlers/user/payment.py
@@ -140,13 +140,26 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
payment_method = payment_info_from_webhook.get("payment_method")
if isinstance(payment_method, dict) and payment_method.get("saved", False):
pm_id = payment_method.get("id")
+ pm_type = payment_method.get("type")
+ title = payment_method.get("title")
card = payment_method.get("card") or {}
+ display_network = None
+ display_last4 = None
+ # Build generic display for various instrument types
+ if (pm_type or "").lower() in {"bank_card", "bank-card", "card"}:
+ display_network = card.get("card_type") or title or "Card"
+ display_last4 = card.get("last4")
+ else:
+ # Wallets, SBP, etc. — use provided title/type; no last4
+ display_network = title or (pm_type.upper() if pm_type else "Payment method")
+ display_last4 = None
+
await user_billing_dal.upsert_yk_payment_method(
session,
user_id=user_id,
payment_method_id=pm_id,
- card_last4=card.get("last4"),
- card_network=card.get("card_type"),
+ card_last4=display_last4,
+ card_network=display_network,
)
except Exception:
logging.exception("Failed to persist YooKassa payment method from webhook")
@@ -463,13 +476,23 @@ async def yookassa_webhook_route(request: web.Request):
user_id = int(user_id_str)
payment_method = payment_dict_for_processing.get("payment_method")
if isinstance(payment_method, dict) and payment_method.get("id"):
+ pm_type = payment_method.get("type")
+ title = payment_method.get("title")
card = payment_method.get("card") or {}
+ display_network = None
+ display_last4 = None
+ if (pm_type or "").lower() in {"bank_card", "bank-card", "card"}:
+ display_network = card.get("card_type") or title or "Card"
+ display_last4 = card.get("last4")
+ else:
+ display_network = title or (pm_type.upper() if pm_type else "Payment method")
+ display_last4 = None
await user_billing_dal.upsert_yk_payment_method(
session,
user_id=user_id,
payment_method_id=payment_method.get("id"),
- card_last4=card.get("last4"),
- card_network=card.get("card_type"),
+ card_last4=display_last4,
+ card_network=display_network,
)
await session.commit()
# Save multi-card entry and mark default if first
@@ -480,8 +503,8 @@ async def yookassa_webhook_route(request: web.Request):
user_id=user_id,
provider_payment_method_id=payment_method.get("id"),
provider="yookassa",
- card_last4=card.get("last4"),
- card_network=card.get("card_type"),
+ card_last4=display_last4,
+ card_network=display_network,
set_default=True,
)
await session.commit()
diff --git a/bot/handlers/user/subscription.py b/bot/handlers/user/subscription.py
index 0d2ca3a..fe005ae 100644
--- a/bot/handlers/user/subscription.py
+++ b/bot/handlers/user/subscription.py
@@ -313,12 +313,21 @@ async def pay_yk_callback_handler(
pm = payment_response_yk.get("payment_method")
try:
if pm and pm.get('id'):
+ pm_type = pm.get('type')
+ title = pm.get('title')
+ card = pm.get('card') or {}
+ if isinstance(card, dict) and (pm_type or '').lower() in {"bank_card", "bank-card", "card"}:
+ display_network = card.get('card_type') or title or 'Card'
+ display_last4 = card.get('last4')
+ else:
+ display_network = title or (pm_type.upper() if pm_type else 'Payment method')
+ display_last4 = None
await user_billing_dal.upsert_yk_payment_method(
session,
user_id=user_id,
payment_method_id=pm['id'],
- card_last4=pm.get('last4'),
- card_network=pm.get('card', {}).get('card_type') if isinstance(pm.get('card'), dict) else None,
+ card_last4=display_last4,
+ card_network=display_network,
)
await session.commit()
except Exception:
@@ -604,7 +613,10 @@ async def payment_methods_manage(callback: types.CallbackQuery, settings: Settin
methods = await list_user_payment_methods(session, callback.from_user.id)
cards: List[tuple] = []
for m in methods:
- title = get_text("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4 or "????")
+ if m.card_last4:
+ title = get_text("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4)
+ else:
+ title = get_text("payment_method_generic_title", network=m.card_network or "Payment method")
cards.append((str(m.method_id), title if not m.is_default else f"⭐ {title}"))
text = get_text("payment_methods_title")
@@ -683,7 +695,10 @@ async def payment_method_delete(callback: types.CallbackQuery, settings: Setting
text = _("payment_methods_title")
cards = []
for m in methods:
- title = _("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4 or "????")
+ if m.card_last4:
+ title = _("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4)
+ else:
+ title = _("payment_method_generic_title", network=m.card_network or "Payment method")
cards.append((str(m.method_id), title if not m.is_default else f"⭐ {title}"))
if not cards:
text += "\n\n" + _("payment_method_none")
@@ -713,7 +728,10 @@ async def payment_method_delete(callback: types.CallbackQuery, settings: Setting
methods = await list_user_payment_methods(session, callback.from_user.id)
cards = []
for m in methods:
- title = _("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4 or "????")
+ if m.card_last4:
+ title = _("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4)
+ else:
+ title = _("payment_method_generic_title", network=m.card_network or "Payment method")
cards.append((str(m.method_id), title if not m.is_default else f"⭐ {title}"))
text = _("payment_methods_title")
if not cards:
@@ -743,7 +761,10 @@ async def payment_method_view(callback: types.CallbackQuery, settings: Settings,
pm_id = parts[2] if len(parts) >= 3 else str(methods[0].method_id)
# Map:
sel = next((m for m in methods if str(m.method_id) == pm_id or m.provider_payment_method_id == pm_id), methods[0])
- title = _("payment_method_card_title", network=sel.card_network or "Card", last4=sel.card_last4 or "????")
+ if sel.card_last4:
+ title = _("payment_method_card_title", network=sel.card_network or "Card", last4=sel.card_last4)
+ else:
+ title = _("payment_method_generic_title", network=sel.card_network or "Payment method")
added_at = sel.created_at.strftime('%Y-%m-%d') if getattr(sel, 'created_at', None) else "—"
# Last tx
last_tx = "—"
@@ -791,7 +812,10 @@ async def payment_method_view(callback: types.CallbackQuery, settings: Settings,
last_tx = last_payment.created_at.strftime('%Y-%m-%d')
except Exception:
pass
- title = _("payment_method_card_title", network=billing.card_network or "Card", last4=billing.card_last4 or "????")
+ if billing.card_last4:
+ title = _("payment_method_card_title", network=billing.card_network or "Card", last4=billing.card_last4)
+ else:
+ title = _("payment_method_generic_title", network=billing.card_network or "Payment method")
details = f"{title}\n{_('payment_method_added_at', date=added_at)}\n{_('payment_method_last_tx', date=last_tx)}"
await callback.message.edit_text(details, reply_markup=get_payment_method_details_keyboard(billing.yookassa_payment_method_id, current_lang, i18n))
try:
@@ -847,7 +871,10 @@ async def payment_methods_list(callback: types.CallbackQuery, settings: Settings
cards: List[tuple] = []
methods = await list_user_payment_methods(session, callback.from_user.id)
for m in methods:
- title = get_text("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4 or "????")
+ if m.card_last4:
+ title = get_text("payment_method_card_title", network=m.card_network or "Card", last4=m.card_last4)
+ else:
+ title = get_text("payment_method_generic_title", network=m.card_network or "Payment method")
cards.append((str(m.method_id), title if not m.is_default else f"⭐ {title}"))
# Parse page
diff --git a/locales/en.json b/locales/en.json
index a171d61..b66d666 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -377,14 +377,15 @@
"subscription_autorenew_updated": "Auto-renew settings updated.",
"payment_methods_manage_button": "💳 Payment Methods",
"payment_methods_title": "💳 Payment Methods",
- "payment_method_bind_button": "➕ Add card",
+ "payment_method_bind_button": "➕ Add method",
"payment_method_delete_button": "🗑 Remove",
"payment_method_view_button": "ℹ️ Details",
- "payment_method_none": "You don't have a saved card yet.",
- "payment_method_bound_success": "✅ Card successfully added.",
+ "payment_method_none": "You don't have a saved payment method yet.",
+ "payment_method_bound_success": "✅ Payment method added.",
"payment_method_deleted_success": "✅ Payment method removed.",
"payment_method_delete_confirm": "Remove saved payment method?",
- "payment_method_card_title": "💳 Card {network} ••••{last4}",
+ "payment_method_card_title": "💳 {network} ••••{last4}",
+ "payment_method_generic_title": "💳 {network}",
"payment_method_added_at": "Added: {date}",
"payment_method_last_tx": "Last transaction: {date}",
"payment_method_tx_history_title": "📜 Transactions history",
diff --git a/locales/ru.json b/locales/ru.json
index d6af159..199436d 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -376,14 +376,15 @@
"subscription_autorenew_updated": "Настройки автопродления обновлены.",
"payment_methods_manage_button": "💳 Способы оплаты",
"payment_methods_title": "💳 Способы оплаты",
- "payment_method_bind_button": "➕ Привязать карту",
+ "payment_method_bind_button": "➕ Добавить способ",
"payment_method_delete_button": "🗑 Удалить",
"payment_method_view_button": "ℹ️ Детали",
- "payment_method_none": "У вас пока нет сохранённой карты.",
- "payment_method_bound_success": "✅ Карта успешно привязана.",
+ "payment_method_none": "У вас пока нет сохранённого способа оплаты.",
+ "payment_method_bound_success": "✅ Способ оплаты добавлен.",
"payment_method_deleted_success": "✅ Способ оплаты удалён.",
"payment_method_delete_confirm": "Удалить сохранённый способ оплаты?",
- "payment_method_card_title": "💳 Карта {network} ••••{last4}",
+ "payment_method_card_title": "💳 {network} ••••{last4}",
+ "payment_method_generic_title": "💳 {network}",
"payment_method_added_at": "Добавлена: {date}",
"payment_method_last_tx": "Последняя операция: {date}",
"payment_method_tx_history_title": "📜 История операций",