Files
Fulfillment-Backend/services/billing.py

201 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from io import BytesIO
from pathlib import Path
from typing import List
from fastapi import HTTPException
from jinja2 import Environment, FileSystemLoader
from number_to_string import get_string_by_number
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from starlette import status
from weasyprint import HTML
import backend.config
from constants import MONTHS, ENV
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema
from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel
from schemas.billing import *
from services.base import BaseService
from services.deal import DealService
from utils.list_utils import to_locale_number
class BillingService(BaseService):
async def _process_update_details(
self,
request: BillStatusUpdateRequest
):
billing_client = BillingClient(backend.config.BILLING_API_KEY)
notify_received_request = NotifyReceivedBillRequestSchema(
listener_transaction_id=request.listener_transaction_id,
channel=NotificationChannel.PAYMENT_DETAILS,
received=True
)
response = await billing_client.notify_received(notify_received_request)
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
if not response.ok:
return
deal_bill_request.pdf_url = request.info.pdf_url
deal_bill_request.invoice_number = request.info.invoice_number
await self.session.commit()
async def _process_update_verification(
self,
request: BillStatusUpdateRequest
):
billing_client = BillingClient(backend.config.BILLING_API_KEY)
notify_received_request = NotifyReceivedBillRequestSchema(
listener_transaction_id=request.listener_transaction_id,
channel=NotificationChannel.PAYMENT_VERIFICATION,
received=True
)
response = await billing_client.notify_received(notify_received_request)
if not response.ok:
return
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
if not deal_bill_request:
return
deal_bill_request.paid = request.info.payed
await self.session.commit()
async def process_update(
self,
request: BillStatusUpdateRequest
):
if request.channel == NotificationChannel.PAYMENT_DETAILS:
await self._process_update_details(request)
elif request.channel == NotificationChannel.PAYMENT_VERIFICATION:
await self._process_update_verification(request)
async def create_deal_billing(self, user, request: CreateDealBillRequest) -> CreateDealBillResponse:
try:
deal_service = DealService(self.session)
billing_client = BillingClient(backend.config.BILLING_API_KEY)
deal: Deal = await deal_service.get_by_id(user, request.deal_id)
billing_request_values: List[CreateBillingRequestValue] = []
for product in deal.products:
for service in product.services:
billing_request_values.append(
CreateBillingRequestValue(
name=f'[{product.product.name}] - {service.service.name}',
price=service.price,
amount=product.quantity
)
)
for service in deal.services:
billing_request_values.append(
CreateBillingRequestValue(
name=f'{service.service.name}',
price=service.price,
amount=service.quantity
)
)
create_bill_request = CreateBillRequestSchema(
listener_transaction_id=deal.id,
payer_name=deal.client.name,
payer_inn=deal.client.details.inn,
payer_phone=deal.client.details.phone_number,
items=CreateBillRequestItems(
values=billing_request_values
)
)
create_bill_response = await billing_client.create(create_bill_request)
if not create_bill_response.ok:
return CreateDealBillResponse(ok=create_bill_response.ok, message='Ошибка!')
deal_bill_request = DealBillRequest(
deal_id=request.deal_id,
created_at=datetime.datetime.now()
)
self.session.add(deal_bill_request)
deal.is_locked = True
await self.session.commit()
return CreateDealBillResponse(ok=create_bill_response.ok, message='Заявка успешно создана!')
except Exception as e:
return CreateDealBillResponse(ok=False, message=str(e))
async def _get_deal_bill_by_id(self, deal_id: int) -> Optional[DealBillRequest]:
return await self.session.scalar(select(DealBillRequest).where(DealBillRequest.deal_id == deal_id))
async def get_deal_bill_by_id(self, deal_id: int) -> GetDealBillById:
deal_bill = await self._get_deal_bill_by_id(deal_id)
if not deal_bill:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Deal bill was not found')
return GetDealBillById(deal_bill=DealBillRequestSchema.model_validate(deal_bill))
async def cancel_deal_billing(self, user, request: CancelDealBillRequest) -> CancelDealBillResponse:
try:
deal_bill = await self._get_deal_bill_by_id(request.deal_id)
if not deal_bill:
return CancelDealBillResponse(ok=False, message='Заявка не найдена')
billing_client = BillingClient(backend.config.BILLING_API_KEY)
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=request.deal_id))
if not response.ok:
return CancelDealBillResponse(ok=False, message='Ошибка')
await self.session.delete(deal_bill)
await self.session.commit()
return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана')
except Exception as e:
return CancelDealBillResponse(ok=False, message=str(e))
async def _get_services_for_deal(self, deal: Deal) -> List[CreateBillingRequestValue]:
services: List[CreateBillingRequestValue] = []
for product in deal.products:
for service in product.services:
services.append(
CreateBillingRequestValue(
name=f'[{product.product.name}] - {service.service.name}',
price=service.price,
amount=product.quantity
)
)
for service in deal.services:
services.append(
CreateBillingRequestValue(
name=f'{service.service.name}',
price=service.price,
amount=service.quantity
)
)
return services
async def _create_billing_document_html(self, deal_id: int):
deal: Deal | None = await self.session.scalar(
select(Deal)
.where(Deal.id == deal_id)
.options(
selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(Deal.services).selectinload(DealServiceModel.service),
)
)
if not deal:
return ""
services = await self._get_services_for_deal(deal)
deal_price = sum((service.price * service.amount for service in services))
deal_price_words = get_string_by_number(deal_price)[0:-10]
deal_price = to_locale_number(deal_price)
template = ENV.get_template("bill-of-payment.html")
now = datetime.datetime.now()
curr_date = f"{now.day} {MONTHS[now.month - 1]} {now.year} г."
return template.render({
"services": services,
"deal_price": deal_price,
"deal_price_words": deal_price_words,
"deal": deal,
"curr_date": curr_date
})
async def create_billing_document_pdf(self, deal_id) -> BytesIO:
doc = await self._create_billing_document_html(deal_id)
pdf_file = BytesIO()
HTML(string=doc).write_pdf(pdf_file)
return pdf_file