Enhance payment method history filtering and YooKassa integration
- Updated the payment method history handler to support filtering by specific saved payment methods, improving user experience when viewing payment logs. - Increased the limit of recent payment logs retrieved from 10 to 30 for better visibility. - Enhanced the YooKassa service to include detailed payment method information, including card details and last four digits, improving clarity in payment history. - Refactored error handling in the YooKassa service to ensure robust fetching of payment information.
This commit is contained in:
@@ -914,15 +914,52 @@ async def payment_method_view(callback: types.CallbackQuery, settings: Settings,
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("pm:history"))
|
@router.callback_query(F.data.startswith("pm:history"))
|
||||||
async def payment_method_history(callback: types.CallbackQuery, settings: Settings, i18n_data: dict, session: AsyncSession):
|
async def payment_method_history(callback: types.CallbackQuery, settings: Settings, i18n_data: dict, session: AsyncSession, yookassa_service: YooKassaService):
|
||||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key
|
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key
|
||||||
|
|
||||||
# Simple history from payments table filtered by user
|
# Simple history from payments table filtered by user
|
||||||
from db.dal import payment_dal
|
from db.dal import payment_dal
|
||||||
payments = await payment_dal.get_recent_payment_logs_with_user(session, limit=10, offset=0)
|
payments = await payment_dal.get_recent_payment_logs_with_user(session, limit=30, offset=0)
|
||||||
user_payments = [p for p in payments if p.user_id == callback.from_user.id]
|
user_payments = [p for p in payments if p.user_id == callback.from_user.id]
|
||||||
|
|
||||||
|
# If viewing a specific saved payment method, filter history by that method when possible
|
||||||
|
selected_pm_provider_id: Optional[str] = None
|
||||||
|
try:
|
||||||
|
_, _, pm_id = callback.data.split(":", 2)
|
||||||
|
if pm_id:
|
||||||
|
# pm_id is our internal method_id; map to provider id
|
||||||
|
from db.dal.user_billing_dal import list_user_payment_methods
|
||||||
|
methods = await list_user_payment_methods(session, callback.from_user.id)
|
||||||
|
sel = next((m for m in methods if str(m.method_id) == pm_id), None)
|
||||||
|
if sel and sel.provider_payment_method_id:
|
||||||
|
selected_pm_provider_id = sel.provider_payment_method_id
|
||||||
|
except Exception:
|
||||||
|
selected_pm_provider_id = None
|
||||||
|
|
||||||
|
if selected_pm_provider_id:
|
||||||
|
# Filter to rows we can confidently associate with the selected method
|
||||||
|
# Heuristics:
|
||||||
|
# 1) Payments with yookassa_payment_id -> fetch payment info and compare payment_method.id
|
||||||
|
# 2) For auto-renew description, it always uses default saved method; keep those too
|
||||||
|
filtered: List[Payment] = []
|
||||||
|
for p in user_payments:
|
||||||
|
if p.provider != 'yookassa':
|
||||||
|
continue
|
||||||
|
if p.yookassa_payment_id and yookassa_service:
|
||||||
|
try:
|
||||||
|
info = await yookassa_service.get_payment_info(p.yookassa_payment_id)
|
||||||
|
pm = (info or {}).get("payment_method") or {}
|
||||||
|
if pm.get("id") == selected_pm_provider_id:
|
||||||
|
filtered.append(p)
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Fallback: auto-renew entries initiated via default method; include them
|
||||||
|
if (p.description or "").lower().startswith("auto-renewal"):
|
||||||
|
filtered.append(p)
|
||||||
|
user_payments = filtered
|
||||||
if not user_payments:
|
if not user_payments:
|
||||||
# Try to get pm_id from context to go one step back
|
# Try to get pm_id from context to go one step back
|
||||||
pm_id = ""
|
pm_id = ""
|
||||||
|
|||||||
@@ -206,19 +206,6 @@ class YooKassaService:
|
|||||||
logging.error(
|
logging.error(
|
||||||
"YooKassa is not configured. Cannot get payment info.")
|
"YooKassa is not configured. Cannot get payment info.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def cancel_payment(self, payment_id_in_yookassa: str) -> bool:
|
|
||||||
if not self.configured:
|
|
||||||
logging.error("YooKassa is not configured. Cannot cancel payment.")
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
await loop.run_in_executor(None, lambda: YooKassaPayment.cancel(payment_id_in_yookassa))
|
|
||||||
logging.info(f"Cancelled YooKassa payment {payment_id_in_yookassa}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Failed to cancel YooKassa payment {payment_id_in_yookassa}: {e}")
|
|
||||||
return False
|
|
||||||
try:
|
try:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Fetching payment info from YooKassa for ID: {payment_id_in_yookassa}"
|
f"Fetching payment info from YooKassa for ID: {payment_id_in_yookassa}"
|
||||||
@@ -232,37 +219,40 @@ class YooKassaService:
|
|||||||
logging.info(
|
logging.info(
|
||||||
f"YooKassa payment info for {payment_id_in_yookassa}: Status={payment_info_yk.status}, Paid={payment_info_yk.paid}"
|
f"YooKassa payment info for {payment_id_in_yookassa}: Status={payment_info_yk.status}, Paid={payment_info_yk.paid}"
|
||||||
)
|
)
|
||||||
|
pm = getattr(payment_info_yk, 'payment_method', None)
|
||||||
|
pm_payload: Dict[str, Any] = {}
|
||||||
|
if pm:
|
||||||
|
# Collect common fields, including id and hints for last4
|
||||||
|
pm_id = getattr(pm, 'id', None)
|
||||||
|
pm_type = getattr(pm, 'type', None)
|
||||||
|
pm_title = getattr(pm, 'title', None)
|
||||||
|
account_number = getattr(pm, 'account_number', None) or getattr(pm, 'account', None)
|
||||||
|
card_obj = getattr(pm, 'card', None)
|
||||||
|
last4_val = None
|
||||||
|
if card_obj and hasattr(card_obj, 'last4'):
|
||||||
|
last4_val = getattr(card_obj, 'last4')
|
||||||
|
elif isinstance(account_number, str) and len(account_number) >= 4:
|
||||||
|
last4_val = account_number[-4:]
|
||||||
|
pm_payload = {
|
||||||
|
"id": pm_id,
|
||||||
|
"type": pm_type,
|
||||||
|
"title": pm_title,
|
||||||
|
"card_last4": last4_val,
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
"id":
|
"id": payment_info_yk.id,
|
||||||
payment_info_yk.id,
|
"status": payment_info_yk.status,
|
||||||
"status":
|
"paid": payment_info_yk.paid,
|
||||||
payment_info_yk.status,
|
"amount_value": float(payment_info_yk.amount.value),
|
||||||
"paid":
|
"amount_currency": payment_info_yk.amount.currency,
|
||||||
payment_info_yk.paid,
|
"metadata": payment_info_yk.metadata,
|
||||||
"amount_value":
|
"description": payment_info_yk.description,
|
||||||
float(payment_info_yk.amount.value),
|
"refundable": payment_info_yk.refundable,
|
||||||
"amount_currency":
|
"created_at": payment_info_yk.created_at.isoformat() if hasattr(
|
||||||
payment_info_yk.amount.currency,
|
payment_info_yk.created_at, 'isoformat') else str(payment_info_yk.created_at),
|
||||||
"metadata":
|
"captured_at": payment_info_yk.captured_at.isoformat() if getattr(payment_info_yk, 'captured_at', None) and hasattr(payment_info_yk.captured_at, 'isoformat') else None,
|
||||||
payment_info_yk.metadata,
|
"payment_method": pm_payload,
|
||||||
"description":
|
"test_mode": getattr(payment_info_yk, 'test', None),
|
||||||
payment_info_yk.description,
|
|
||||||
"refundable":
|
|
||||||
payment_info_yk.refundable,
|
|
||||||
"created_at":
|
|
||||||
payment_info_yk.created_at.isoformat() if hasattr(
|
|
||||||
payment_info_yk.created_at, 'isoformat') else str(
|
|
||||||
payment_info_yk.created_at),
|
|
||||||
"captured_at":
|
|
||||||
payment_info_yk.captured_at.isoformat()
|
|
||||||
if payment_info_yk.captured_at and hasattr(
|
|
||||||
payment_info_yk.captured_at, 'isoformat') else None,
|
|
||||||
"payment_method_type":
|
|
||||||
payment_info_yk.payment_method.type
|
|
||||||
if payment_info_yk.payment_method else None,
|
|
||||||
"test_mode":
|
|
||||||
payment_info_yk.test
|
|
||||||
if hasattr(payment_info_yk, 'test') else None
|
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
@@ -274,3 +264,16 @@ class YooKassaService:
|
|||||||
f"YooKassa get payment info for {payment_id_in_yookassa} failed: {e}",
|
f"YooKassa get payment info for {payment_id_in_yookassa} failed: {e}",
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def cancel_payment(self, payment_id_in_yookassa: str) -> bool:
|
||||||
|
if not self.configured:
|
||||||
|
logging.error("YooKassa is not configured. Cannot cancel payment.")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
await loop.run_in_executor(None, lambda: YooKassaPayment.cancel(payment_id_in_yookassa))
|
||||||
|
logging.info(f"Cancelled YooKassa payment {payment_id_in_yookassa}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to cancel YooKassa payment {payment_id_in_yookassa}: {e}")
|
||||||
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user