feat: billing guest access

This commit is contained in:
2024-08-08 07:49:53 +03:00
parent a7c4fabed0
commit 97f835ffde
30 changed files with 682 additions and 140 deletions

View File

@@ -19,8 +19,10 @@ oauth2_schema = HTTPBearer()
algorithm = 'HS256'
async def get_current_user(session: Annotated[AsyncSession, Depends(get_session)],
token: Annotated[HTTPAuthorizationCredentials, Depends(oauth2_schema)]) -> User | None:
async def get_current_user(
session: Annotated[AsyncSession, Depends(get_session)],
token: Annotated[HTTPAuthorizationCredentials, Depends(oauth2_schema)]
) -> Union[User, None, dict]:
if not token.credentials:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
try:
@@ -28,6 +30,8 @@ async def get_current_user(session: Annotated[AsyncSession, Depends(get_session)
user_id = payload.get('sub')
if not user_id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid credentials')
if user_id == 'guest':
return payload
user_id = int(user_id)
user = await session.get(User, user_id)
@@ -35,16 +39,26 @@ async def get_current_user(session: Annotated[AsyncSession, Depends(get_session)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid credentials')
return user
except JWTError as e:
print(e)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
async def authorized_user(
user: Annotated[User, Depends(get_current_user)]
):
if type(user) is User:
return user
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
async def guest_user(user: Annotated[User, Depends(get_current_user)]):
if (type(user) is User) or (type(user) is dict):
return user
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
class AuthService(BaseService):
@staticmethod
def _generate_jwt_token(user: User) -> str:
payload = {
'sub': str(user.id)
}
def _generate_jwt_token(payload: dict) -> str:
return jwt.encode(payload, backend.config.SECRET_KEY, algorithm=algorithm)
async def authenticate(self, request: AuthLoginRequest):
@@ -60,5 +74,16 @@ class AuthService(BaseService):
)
self.session.add(user)
await self.session.commit()
access_token = self._generate_jwt_token(user)
payload = {
'sub': str(user.id)
}
access_token = self._generate_jwt_token(payload)
return AuthLoginResponse(access_token=access_token)
def create_deal_guest_token(self, deal_id: int):
payload = {
'sub': 'guest',
'deal_id': deal_id
}
return self._generate_jwt_token(payload)

118
services/billing.py Normal file
View File

@@ -0,0 +1,118 @@
import datetime
from typing import List
from fastapi import HTTPException
from sqlalchemy import select
from starlette import status
import backend.config
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, BillPaymentInfo
from models import DealBillRequest, Deal
from schemas.billing import *
from services.base import BaseService
from services.deal import DealService
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))

View File

@@ -1,16 +1,16 @@
import lexorank
import models.secondary
from typing import Union
import models.deal
from fastapi import HTTPException
from sqlalchemy import select, func, update, delete, insert
from sqlalchemy.orm import joinedload, selectinload
from starlette import status
import models.deal
import models.secondary
from models import User, Service, Client
from models.deal import *
from schemas.client import ClientDetailsSchema
from schemas.deal import *
from services.auth import AuthService
from services.base import BaseService
from services.client import ClientService
from services.service import ServiceService
@@ -20,6 +20,15 @@ from services.shipping_warehouse import ShippingWarehouseService
class DealService(BaseService):
# region Deal
@staticmethod
def grant_access(user: Union[models.User, dict], deal_id):
if type(user) is models.User:
return
user_deal_id = user['deal_id']
if int(user_deal_id) != int(deal_id):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
return await self.session.get(Deal, deal_id)
@@ -211,7 +220,9 @@ class DealService(BaseService):
result.append(DealSchema.model_validate(deal))
return DealGetAllResponse(deals=result)
async def get_by_id(self, deal_id: int) -> DealSchema:
async def get_by_id(self, user: Union[models.User, dict], deal_id: int, return_raw=False) -> Union[
DealSchema, models.Deal]:
self.grant_access(user, deal_id)
deal = await self.session.scalar(
select(Deal)
@@ -244,7 +255,8 @@ class DealService(BaseService):
)
.where(Deal.id == deal_id)
)
if return_raw:
return deal
if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена")
return DealSchema.model_validate(deal)
@@ -360,11 +372,17 @@ class DealService(BaseService):
except Exception as e:
return DealAddKitResponse(ok=False, message=str(e))
def create_guest_url(self, user: models.User, request: DealCreateGuestUrlRequest) -> DealCreateGuestUrlResponse:
if not user.is_admin:
return DealCreateGuestUrlResponse(ok=False, message='Создать ссылку может только администратор', url="")
access_token = AuthService(self.session).create_deal_guest_token(request.deal_id)
url = f"deals/{request.deal_id}?accessToken={access_token}"
return DealCreateGuestUrlResponse(ok=True, message='Ссылка успешно создана!', url=url)
# endregion
# region Deal services
async def add_services(self, request: DealAddServicesRequest):
# TODO refactor
deal: Deal = await self.session.scalar(
select(Deal)
.options(selectinload(Deal.services))
@@ -404,9 +422,13 @@ class DealService(BaseService):
await self.session.commit()
return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены')
async def update_service_quantity(self,
request: DealUpdateServiceQuantityRequest) -> DealUpdateServiceQuantityResponse:
async def update_service_quantity(
self,
user: Union[models.User, dict],
request: DealUpdateServiceQuantityRequest
) -> DealUpdateServiceQuantityResponse:
try:
self.grant_access(user, request.deal_id)
deal_service = await self.session.scalar(
select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id,
@@ -421,8 +443,12 @@ class DealService(BaseService):
await self.session.rollback()
return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
async def add_service(self, request: DealAddServiceRequest) -> DealAddServiceResponse:
async def add_service(self,
user: Union[models.User, dict],
request: DealAddServiceRequest
) -> DealAddServiceResponse:
try:
self.grant_access(user, request.deal_id)
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена")
@@ -450,8 +476,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealAddServiceResponse(ok=False, message=str(e))
async def delete_service(self, request: DealDeleteServiceRequest) -> DealDeleteServiceResponse:
async def delete_service(
self,
user: Union[models.User, dict],
request: DealDeleteServiceRequest
) -> DealDeleteServiceResponse:
try:
self.grant_access(user, request.deal_id)
deal_service = await self.session.scalar(
select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id,
@@ -466,8 +497,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealDeleteServiceResponse(ok=False, message=str(e))
async def delete_services(self, request: DealDeleteServicesRequest) -> DealDeleteServicesResponse:
async def delete_services(
self,
user: Union[models.User, dict],
request: DealDeleteServicesRequest
) -> DealDeleteServicesResponse:
try:
self.grant_access(user, request)
deal_services = await self.session.scalars(
select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id,
@@ -481,8 +517,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealDeleteServicesResponse(ok=False, message=str(e))
async def update_service(self, request: DealUpdateServiceRequest) -> DealUpdateServiceResponse:
async def update_service(
self,
user: Union[models.User, dict],
request: DealUpdateServiceRequest
) -> DealUpdateServiceResponse:
try:
self.grant_access(user, request.deal_id)
deal_service = await self.session.scalar(
select(models.secondary.DealService)
.where(models.secondary.DealService.deal_id == request.deal_id,
@@ -535,9 +576,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
async def copy_services(self, request: DealServicesCopyRequest) -> DealServicesCopyResponse:
async def copy_services(
self,
user: Union[models.User, dict],
request: DealServicesCopyRequest
) -> DealServicesCopyResponse:
try:
self.grant_access(user, request.deal_id)
source_services_stmt = (
select(
models.DealProductService
@@ -614,9 +659,13 @@ class DealService(BaseService):
# endregion
# region Deal products
async def update_product_quantity(self,
request: DealUpdateProductQuantityRequest) -> DealUpdateProductQuantityResponse:
async def update_product_quantity(
self,
user: Union[models.User, dict],
request: DealUpdateProductQuantityRequest
) -> DealUpdateProductQuantityResponse:
try:
self.grant_access(user, request.deal_id)
# check if there is no deal or no product with different exceptions
deal_product = await self.session.scalar(
select(models.secondary.DealProduct)
@@ -632,8 +681,15 @@ class DealService(BaseService):
await self.session.rollback()
return DealUpdateProductQuantityResponse(ok=False, message=str(e))
async def add_product(self, request: DealAddProductRequest) -> DealAddProductResponse:
async def add_product(
self,
user: Union[models.User, dict],
request: DealAddProductRequest
) -> DealAddProductResponse:
try:
self.grant_access(user, request.deal_id)
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена")
@@ -670,8 +726,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealAddProductResponse(ok=False, message=str(e))
async def delete_product(self, request: DealDeleteProductRequest) -> DealDeleteProductResponse:
async def delete_product(
self,
user: Union[models.User, dict],
request: DealDeleteProductRequest
) -> DealDeleteProductResponse:
try:
self.grant_access(user, request.deal_id)
deal_product = await self.session.scalar(
select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id,
@@ -686,8 +747,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealDeleteProductResponse(ok=False, message=str(e))
async def delete_products(self, request: DealDeleteProductsRequest) -> DealDeleteProductsResponse:
async def delete_products(
self,
user: Union[models.User, dict],
request: DealDeleteProductsRequest
) -> DealDeleteProductsResponse:
try:
self.grant_access(user, request.deal_id)
deal_products = await self.session.scalars(
select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id,
@@ -701,8 +767,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealDeleteProductsResponse(ok=False, message=str(e))
async def update_product(self, request: DealUpdateProductRequest):
async def update_product(
self,
user: Union[models.User, dict],
request: DealUpdateProductRequest
):
try:
self.grant_access(user, request.deal_id)
deal_product: models.DealProduct = await self.session.scalar(
select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id,
@@ -781,8 +852,13 @@ class DealService(BaseService):
await self.session.rollback()
return DealUpdateProductResponse(ok=False, message=str(e))
async def add_kit_to_deal_product(self, request: DealProductAddKitRequest) -> DealProductAddKitResponse:
async def add_kit_to_deal_product(
self,
user: Union[models.User, dict],
request: DealProductAddKitRequest
) -> DealProductAddKitResponse:
try:
self.grant_access(user, request.deal_id)
service_service = ServiceService(self.session)
kit = await service_service.get_kit_by_id(request.kit_id)
if not kit:

View File

@@ -1,3 +1,5 @@
from typing import Union
from fastapi import HTTPException
from sqlalchemy import select, func, Integer, update
from sqlalchemy.orm import selectinload
@@ -5,25 +7,17 @@ from sqlalchemy.orm import selectinload
import utils.barcodes
from backend import config
from external.s3_uploader.uploader import S3Uploader
from models.product import Product, ProductBarcode, ProductImage
from models import User
from models.product import Product, ProductImage
from schemas.base import PaginationSchema
from services.base import BaseService
from schemas.product import *
from services.base import BaseService
from utils.dependecies import is_valid_pagination
class ProductService(BaseService):
async def create(self, request: ProductCreateRequest) -> ProductCreateResponse:
# Unique article validation
# existing_product_query = await self.session.execute(
# select(Product)
# .where(Product.client_id == request.client_id,
# Product.article == request.article)
# )
# existing_product = existing_product_query.first()
# if existing_product:
# return ProductCreateResponse(ok=False, message='Товар с таким артикулом уже существует у клиента')
# Creating product
product_dict = request.dict()