144 lines
5.8 KiB
Python
144 lines
5.8 KiB
Python
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 |