@@ -89,4 +89,19 @@ PATH_PREFETCH_MAP = { ), ), }, + '/api/v2/payments/plans': { + 'select': ( + 'host_account', + 'payment_plan', + 'business_account__parent_company__user__payment_plan__plan', + 'payment_plan__plan', + 'payment_plan__method', + ), + 'prefetch': ( + Prefetch( + 'business_account__parent_company__user__payment_plan__plan', + queryset=NeuronModel.objects.only('slug'), + ), + ), + }, } @@ -18,15 +18,20 @@ from backend.public import urlpatterns as public_urlpatterns api = NinjaAPI(title='AIR API', version='1.0.0', docs_url=None) compatibility_api = NinjaAPI(title='AIR API DEBUG', version='0.0.1', docs_url=None) +compatibility_api_v2 = NinjaAPI(title='AIR API DEBUG', version='0.0.2', docs_url=None) + api.add_router('copywrite/', 'tools.copywrite.routes.v1.router') api.add_router('users/', 'users.routes.v1.router') api.add_router('chats/', 'tools.chats.routes.v1.router') api.add_router('media/', 'tools.media.routes.v1.router') + compatibility_api.add_router('auth/', 'authentication.routes.v1.router') compatibility_api.add_router('payments/', 'payments.routes.v1.router') compatibility_api.add_router('reports/', 'reports.routes.v1.router') compatibility_api.add_router('ml_model/', 'ml_model.routes.v1.router') +compatibility_api_v2.add_router('payments/', 'payments.routes.v2.router') + logger = logging.getLogger(__name__) @@ -62,6 +67,7 @@ urlpatterns += [ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) api.docs_url = '/docs' compatibility_api.docs_url = '/docs' +compatibility_api_v2.docs_url = '/docs' urlpatterns += [ path('api/v1/healthz/', healthz_status), @@ -84,6 +90,7 @@ urlpatterns += [ ), path('api/v1/api/', api.urls), path('api/v1/', compatibility_api.urls), + path('api/v2/', compatibility_api_v2.urls), ] urlpatterns += public_urlpatterns @@ -230,7 +230,7 @@ async def create_payment_link(request, body: NewSubscriptionSchema): if request.auth.account_type not in {'regular', 'business_host'}: raise HttpError(401, 'Unauthorized') payment_plan = await PaymentPlan.objects.aget(uid=body.uid) - payment_url = await PaymentService(request.auth).create_payment_link(payment_plan) + payment_url = (await PaymentService(request.auth).create_payment_link(payment_plan))[0] logger.info( 'Payment link endpoint completed: email=%s plan_uid=%s', request.auth.email, @@ -0,0 +1,33 @@ +import logging + +from ninja import Router +from ninja.errors import HttpError + +from authentication.security import AsyncAuthBearer, SyncAuthBearer + +from payments.models import PaymentPlan +from payments.schemas import NewSubscriptionSchema, PaymentLinkPriceSchema +from payments.services.payment_service import PaymentService + +router = Router(auth=SyncAuthBearer(), tags=['payments']) + +logger = logging.getLogger(__name__) + + +@router.post('plans', tags=['payments/plans'], auth=AsyncAuthBearer(), response=PaymentLinkPriceSchema) +async def create_payment_link(request, body: NewSubscriptionSchema): + try: + if request.auth.account_type not in {'regular', 'business_host'}: + raise HttpError(401, 'Unauthorized') + payment_plan = await PaymentPlan.objects.aget(uid=body.uid) + payment_url, plan_price, plan_tokens = await PaymentService(request.auth).create_payment_link( + payment_plan + ) + logger.info( + 'Payment link endpoint completed: email=%s plan_uid=%s', + request.auth.email, + payment_plan.uid, + ) + return PaymentLinkPriceSchema(payment_url=payment_url, price=plan_price, buying_tokens=plan_tokens) + except Exception as exc: + raise HttpError(400, f'{exc}') \ No newline at end of file @@ -30,7 +30,7 @@ class PaymentService: def __init__(self, user: CustomUserModel): self.user = user - async def create_payment_link(self, plan: PaymentPlan) -> str: + async def create_payment_link(self, plan: PaymentPlan) -> tuple[str, Decimal, Decimal]: current_plan = self.user.payment_plan.plan current_balance = self.user.payment_plan.current_token_balance plan_price = plan.price @@ -47,7 +47,7 @@ class PaymentService: tokens_diff = current_plan.tokens_per_plan - current_balance additional_tokens = 0 if tokens_diff < 0 else tokens_diff plan_price = Decimal(plans_price_diff + (token_price * additional_tokens)).quantize( - Decimal('0.01'), rounding='ROUND_UP' + Decimal('1'), rounding='ROUND_UP' ) balance_sufficient = (plan_tokens - current_balance) <= 0 plan_tokens = ( @@ -94,7 +94,11 @@ class PaymentService: plan_price, is_recurring, ) - return payment.confirmation.confirmation_url + return ( + payment.confirmation.confirmation_url, + plan_price, + plan_tokens.quantize(Decimal('1'), rounding='ROUND_UP'), + ) @sync_to_async def do_payment(self, payment: YookassaPaymentResponse) -> PaymentModel: @@ -1,4 +1,5 @@ from datetime import date, datetime +from decimal import Decimal from typing import List, Optional from uuid import UUID @@ -76,4 +77,9 @@ class ExpensesParamsSchema(Schema): class ExpensesSchema(Schema): source: str - amount: condecimal(max_digits=10, decimal_places=2) \ No newline at end of file + amount: condecimal(max_digits=10, decimal_places=2) + + +class PaymentLinkPriceSchema(PaymentLinkSchema): + price: Decimal + buying_tokens: Decimal