Enhance payment processing and user detail updates in YooKassa integration
- Added handling for 'waiting_for_capture' event in the YooKassa webhook to manage bind-only payment flows, including saving payment methods and canceling authorizations. - Introduced a new method in the YooKassaService to cancel payments, improving error handling and logging for payment cancellations. - Updated user detail synchronization to conditionally update descriptions only when they differ from the current panel state, enhancing efficiency.
This commit is contained in:
@@ -144,7 +144,10 @@ async def perform_sync(panel_service: PanelApiService, session: AsyncSession,
|
|||||||
existing_user.first_name or "",
|
existing_user.first_name or "",
|
||||||
existing_user.last_name or "",
|
existing_user.last_name or "",
|
||||||
])
|
])
|
||||||
if description_text.strip():
|
# Update description only when it differs from the current one on panel
|
||||||
|
current_panel_description = (panel_user_dict.get("description") or "").strip()
|
||||||
|
desired_description = description_text.strip()
|
||||||
|
if desired_description and desired_description != current_panel_description:
|
||||||
await panel_service.update_user_details_on_panel(
|
await panel_service.update_user_details_on_panel(
|
||||||
panel_uuid, {"description": description_text}
|
panel_uuid, {"description": description_text}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ payment_processing_lock = asyncio.Lock()
|
|||||||
|
|
||||||
YOOKASSA_EVENT_PAYMENT_SUCCEEDED = 'payment.succeeded'
|
YOOKASSA_EVENT_PAYMENT_SUCCEEDED = 'payment.succeeded'
|
||||||
YOOKASSA_EVENT_PAYMENT_CANCELED = 'payment.canceled'
|
YOOKASSA_EVENT_PAYMENT_CANCELED = 'payment.canceled'
|
||||||
|
YOOKASSA_EVENT_PAYMENT_WAITING_FOR_CAPTURE = 'payment.waiting_for_capture'
|
||||||
|
|
||||||
|
|
||||||
async def process_successful_payment(session: AsyncSession, bot: Bot,
|
async def process_successful_payment(session: AsyncSession, bot: Bot,
|
||||||
@@ -451,6 +452,34 @@ async def yookassa_webhook_route(request: web.Request):
|
|||||||
session, bot, payment_dict_for_processing,
|
session, bot, payment_dict_for_processing,
|
||||||
i18n_instance, settings)
|
i18n_instance, settings)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
elif notification_object.event == YOOKASSA_EVENT_PAYMENT_WAITING_FOR_CAPTURE:
|
||||||
|
# Bind-only flow: save method and cancel auth if metadata has bind_only
|
||||||
|
metadata = payment_dict_for_processing.get("metadata", {}) or {}
|
||||||
|
if metadata.get("bind_only") == "1":
|
||||||
|
try:
|
||||||
|
user_id_str = metadata.get("user_id")
|
||||||
|
if user_id_str and user_id_str.isdigit():
|
||||||
|
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"):
|
||||||
|
card = payment_method.get("card") or {}
|
||||||
|
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"),
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
# Attempt to cancel the authorization to avoid charge hold
|
||||||
|
try:
|
||||||
|
yk: YooKassaService = request.app.get('yookassa_service')
|
||||||
|
if yk:
|
||||||
|
await yk.cancel_payment(payment_dict_for_processing.get("id"))
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to cancel bind-only payment auth")
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to handle bind-only waiting_for_capture webhook")
|
||||||
except Exception as e_webhook_db_processing:
|
except Exception as e_webhook_db_processing:
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
logging.error(
|
logging.error(
|
||||||
|
|||||||
@@ -206,6 +206,19 @@ 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}"
|
||||||
|
|||||||
Reference in New Issue
Block a user