import uuid import logging from yookassa import Configuration, Payment from yookassa.domain.request.payment_request_builder import PaymentRequestBuilder from typing import Optional, Dict, Any, List from config.settings import Settings class YooKassaService: def __init__(self, shop_id: Optional[str], secret_key: Optional[str], configured_return_url: Optional[str], bot_username_for_default: Optional[str] = None, settings_obj: Optional[Settings] = None): self.settings = settings_obj if not shop_id or not secret_key: logging.warning( "YooKassa SHOP_ID or SECRET_KEY not configured. Payment functionality will be disabled." ) self.configured = False else: Configuration.account_id = shop_id Configuration.secret_key = secret_key self.configured = True logging.info(f"YooKassa configured for shop_id: {shop_id}") if configured_return_url: self.return_url = configured_return_url elif bot_username_for_default: self.return_url = f"https://t.me/{bot_username_for_default}" logging.info( f"YOOKASSA_RETURN_URL not set, using dynamic default: {self.return_url}" ) else: self.return_url = "https://example.com/payment_error_no_return_url" logging.warning( f"YOOKASSA_RETURN_URL not set AND bot username not provided. Using placeholder: {self.return_url}" ) logging.info( f"YooKassa Service effective return_url: {self.return_url}") async def create_payment( self, amount: float, currency: str, description: str, metadata: Dict[str, Any]) -> Optional[Dict[str, Any]]: if not self.configured: logging.error("YooKassa is not configured. Cannot create payment.") return None if not self.settings: logging.error( "YooKassaService: Settings object not available for receipt creation." ) return { "error": True, "internal_message": "Service settings not initialized." } if not self.settings.YOOKASSA_DEFAULT_RECEIPT_EMAIL: logging.error( "CRITICAL: YOOKASSA_DEFAULT_RECEIPT_EMAIL is not configured. YooKassa payment will fail due to missing receipt customer contact." ) return { "error": True, "internal_message": "YooKassa receipt email not configured by admin." } try: builder = PaymentRequestBuilder() builder.set_amount({ "value": str(round(amount, 2)), "currency": currency.upper() }) builder.set_capture(True) builder.set_confirmation({ "type": "redirect", "return_url": self.return_url }) builder.set_description(description) builder.set_metadata(metadata) receipt_items: List[Dict[str, Any]] = [{ "description": description, "quantity": "1.00", "amount": { "value": str(round(amount, 2)), "currency": currency.upper() }, "vat_code": str(self.settings.YOOKASSA_VAT_CODE), "payment_mode": self.settings.YOOKASSA_PAYMENT_MODE, "payment_subject": self.settings.YOOKASSA_PAYMENT_SUBJECT }] receipt_customer: Dict[str, str] = {} if self.settings.YOOKASSA_DEFAULT_RECEIPT_EMAIL: receipt_customer[ "email"] = self.settings.YOOKASSA_DEFAULT_RECEIPT_EMAIL if not receipt_customer: logging.error( "YooKassa: No customer contact (email/phone) for receipt.") return { "error": True, "internal_message": "Receipt customer contact missing." } receipt_payload: Dict[str, Any] = { "customer": receipt_customer, "items": receipt_items } builder.set_receipt(receipt_payload) idempotence_key = str(uuid.uuid4()) payment_request = builder.build() logging.info( f"Creating YooKassa payment (IDK: {idempotence_key}) with receipt. Email: {self.settings.YOOKASSA_DEFAULT_RECEIPT_EMAIL}" ) res = Payment.create(payment_request, idempotence_key) logging.info( f"YooKassa Payment.create response: ID={res.id}, Status={res.status}" ) return { "id": res.id, "confirmation_url": res.confirmation.confirmation_url if res.confirmation else None, "status": res.status, "metadata": res.metadata, "amount_value": float(res.amount.value), "amount_currency": res.amount.currency, "idempotence_key": idempotence_key, "paid": res.paid, "refundable": res.refundable, "created_at": res.created_at.isoformat() if hasattr( res.created_at, 'isoformat') else str(res.created_at) } except Exception as e: logging.error(f"YooKassa payment creation failed: {e}", exc_info=True) return None async def get_payment_info(self, payment_id: str) -> Optional[Dict[str, Any]]: if not self.configured: logging.error( "YooKassa is not configured. Cannot get payment info.") return None try: payment_info = Payment.find_one(payment_id) if payment_info: return { "id": payment_info.id, "status": payment_info.status, "paid": payment_info.paid, "amount_value": float(payment_info.amount.value), "amount_currency": payment_info.amount.currency, "metadata": payment_info.metadata, "description": payment_info.description, } return None except Exception as e: logging.error( f"YooKassa get payment info for {payment_id} failed: {e}") return None