wallet/apps/wallet/views/application.py
Sayyid Hamid Mahdavi 696fe3c4e5 some cleanup
2025-10-21 19:58:05 +03:30

476 lines
20 KiB
Python
Executable file

import logging
from django.db.models import Q
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
from rest_framework import generics, permissions, mixins
from rest_framework.exceptions import APIException
from rest_framework.generics import get_object_or_404
from rest_framework.request import Request
from rest_framework.response import Response
from apps.users.models import User
from apps.wallet.constans import TypeChoices, StateChoices
from apps.wallet.models import Transaction, Category, Wallet
from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \
TransactionSerializer, BalanceSerializer
from utils.exceptions import UnprocessableEntity
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
def get_application(request):
try:
application = request.auth.application
except:
application = None
return application
def get_wallet_category(request: Request):
pass
class BalanceAPIView(generics.RetrieveAPIView):
'''
API for getting specified user or application balance.
'''
permission_classes = [TokenHasScope]
serializer_class = BalanceSerializer
required_scopes = ['wallet.application:get_balance']
def get_object(self):
return get_application(self.request)
class UserBalanceAPIView(generics.RetrieveAPIView):
'''
API for getting specified user or application balance.
'''
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = BalanceSerializer
required_scopes = ['wallet.application:get_user_balance']
def get_object(self):
user_uuid = self.kwargs.get('uuid')
user, created = User.objects.get_or_create(uuid=user_uuid)
# user = get_object_or_404(User.objects.all(), uuid=user_uuid)
return user
class TransactionDetailAPIView(generics.RetrieveAPIView):
permission_classes = [TokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application:get_transaction_detail']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(Q(application=application) |
Q(payer_id=application.pk, payer_type=TypeChoices.APPLICATION.value) |
Q(payee_id=application.pk, payee_type=TypeChoices.APPLICATION.value)
).all()
class TransactionListAPIView(generics.ListAPIView):
permission_classes = [TokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application:get_transaction_list']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(Q(application=application) |
Q(payer_id=application.pk, payer_type=TypeChoices.APPLICATION.value) |
Q(payee_id=application.pk, payee_type=TypeChoices.APPLICATION.value)
).all()
# from application to another entity
# TODO: better name
class DepositSubmitAPIView(generics.CreateAPIView):
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = ApplicationDepositSerializer
required_scopes = ['wallet.application.deposit:submit']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application,
payer_id=application.pk,
payer_type=TypeChoices.APPLICATION.value).all()
def perform_create(self, serializer):
# TODO: get_or_create instead of create if uuid given
# TODO: it is possible to merage create and submit in one step for improve performance
application = get_application(self.request)
instance = serializer.save(payer_id=application.pk,
payer_type=TypeChoices.APPLICATION.value,
application=application)
try:
instance.category_submit()
except Exception as e:
print(e)
APIException('Failed to submit deposit')
class DepositVerifyAPIView(generics.RetrieveAPIView):
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = ApplicationDepositSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application.deposit:verify']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application,
payer_id=application.pk,
payer_type=TypeChoices.APPLICATION.value).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
if instance.state == StateChoices.PENDING:
instance.category_verify()
serializer = self.get_serializer(instance)
return Response(serializer.data)
# from another entity to application
class WithdrawSubmitAPIView(generics.CreateAPIView):
permission_classes = [TokenHasScope]
serializer_class = ApplicationWithdrawSerializer
required_scopes = ['wallet.application.withdraw:submit']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application,
payee_id=application.pk,
payee_type=TypeChoices.APPLICATION.value).all()
def perform_create(self, serializer):
# TODO: get_or_create instead of create if uuid given
# TODO: it is possible to merage create and submit in one step for improve performance
application = get_application(self.request)
instance = serializer.save(payee_id=application.pk,
payee_type=TypeChoices.APPLICATION.value,
application=application)
instance.category_submit()
class WithdrawVerifyAPIView(generics.RetrieveAPIView):
permission_classes = [TokenHasScope]
serializer_class = ApplicationWithdrawSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application.withdraw:verify']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application,
payee_id=application.pk,
payee_type=TypeChoices.APPLICATION.value).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
if instance.state == StateChoices.PENDING:
instance.category_verify()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CategoryBalanceAPIView(generics.RetrieveAPIView):
'''
API for getting specified user or application balance.
'''
permission_classes = [TokenHasScope]
serializer_class = BalanceSerializer
required_scopes = ['wallet.application:get_balance']
def get_object(self):
category_uuid = self.kwargs.get('category_uuid')
if category_uuid:
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
else:
category = None
application = get_application(self.request)
wallet, created = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
category=category)
return wallet
class CategoryUserBalanceAPIView(generics.RetrieveAPIView):
'''
API for getting specified user or application balance.
'''
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = BalanceSerializer
required_scopes = ['wallet.application:get_user_balance']
def get_object(self):
category_uuid = self.kwargs.get('category_uuid')
if category_uuid:
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
else:
category = None
user_uuid = self.kwargs.get('uuid')
user, created = User.objects.get_or_create(uuid=user_uuid)
application = get_application(self.request)
wallet, created = Wallet.objects.get_or_create(owner_uuid=user_uuid, owner_type=TypeChoices.USER,
category=category)
return wallet
class CategoryTransactionDetailAPIView(generics.RetrieveAPIView):
permission_classes = [TokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application:get_transaction_detail']
def get_queryset(self):
# TODO: what about category
application = get_application(self.request)
return Transaction.objects.filter(Q(application=application) |
Q(payer_wallet__owner_uuid=application.pk,
payer_wallet__owner_type=TypeChoices.APPLICATION) |
Q(payee_wallet__owner_uuid=application.pk,
payee_wallet__owner_type=TypeChoices.APPLICATION)
).all()
class CategoryTransactionListAPIView(generics.ListAPIView):
permission_classes = [TokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application:get_transaction_list']
def get_queryset(self):
# TODO: what about category?
application = get_application(self.request)
return Transaction.objects.filter(Q(application=application) |
Q(payer_wallet__owner_uuid=application.pk,
payer_wallet__owner_type=TypeChoices.APPLICATION) |
Q(payee_wallet__owner_uuid=application.pk,
payee_wallet__owner_type=TypeChoices.APPLICATION)
).all()
# from application to another entity
# TODO: better name
class CategoryDepositSubmitAPIView(generics.CreateAPIView):
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = ApplicationDepositSerializer
required_scopes = ['wallet.application.deposit:submit']
def get_queryset(self):
category_uuid = self.kwargs.get('category_uuid')
if category_uuid:
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
else:
category = None
application = get_application(self.request)
wallet, created = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
category=category)
return Transaction.objects.filter(application=application, payer_wallet=wallet).all()
def perform_create(self, serializer):
# TODO: get_or_create instead of create if uuid given
# TODO: it is possible to merage create and submit in one step for improve performance
logger.info("Starting perform_create for Transaction")
application = get_application(self.request)
logger.debug(f"Application UUID: {application.uuid}")
payee_wallet_category_uuid = serializer.validated_data.pop('payee_wallet_category')
payee_wallet_category_uuid = payee_wallet_category_uuid and str(payee_wallet_category_uuid)
payer_wallet_category_uuid = self.kwargs.get('category_uuid')
if payer_wallet_category_uuid:
payer_wallet_category = get_object_or_404(Category.objects.all(), uuid=payer_wallet_category_uuid)
else:
payer_wallet_category = None
payee_wallet_category = get_object_or_404(Category.objects.all(), uuid=payee_wallet_category_uuid)
if not (payee_wallet_category and payee_wallet_category.currency == payer_wallet_category.currency):
raise UnprocessableEntity('payer and payee wallet currency not equal')
payee_id = serializer.validated_data.pop('payee')
payee_type = serializer.validated_data.pop('payee_type')
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
category=payer_wallet_category)
logger.debug(f"Payer wallet: {payer_wallet.uuid}")
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payee_id, owner_type=payee_type,
category=payee_wallet_category)
logger.debug(f"Payee wallet: {payee_wallet.uuid}")
uuid = serializer.validated_data.pop('uuid', None)
details = serializer.validated_data.get('details', {})
reference_id = details.get('reference_id', None)
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
transaction_params = {
'application': application,
'payer_wallet': payer_wallet,
'payee_wallet': payee_wallet,
'amount': serializer.validated_data.get('amount'),
}
if uuid:
transaction_params['uuid'] = uuid
if reference_id:
transaction_params['details__reference_id'] = reference_id
# else:
# logger.error('Missing UUID and Reference ID')
# raise UnprocessableEntity('Missing UUID or reference_id')
instance, created = Transaction.objects.get_or_create(**transaction_params, defaults={
'details': details
})
serializer.instance = instance
logger.info(f"Transaction {'created' if created else 'retrieved'} successfully: {instance.uuid}")
if instance.state == StateChoices.PENDING:
return
# TODO: status code 200 if not created before
if created or instance.state == StateChoices.CREATED:
instance.category_submit()
else:
logger.warning(f"Transaction was not in processable state! for {instance.uuid}: {instance.state}")
raise UnprocessableEntity(_('Transaction was not in processable state!'))
class CategoryDepositVerifyAPIView(generics.RetrieveAPIView):
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = ApplicationDepositSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application.deposit:verify']
def get_queryset(self):
application = get_application(self.request)
category_uuid = self.kwargs.get('category_uuid')
if category_uuid:
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
else:
category = None
wallet = Wallet.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, category=category)
return Transaction.objects.filter(application=application, payer_wallet=wallet).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
if instance.state == StateChoices.PENDING:
instance.category_verify()
logger.warning(f"Transaction was retrieved for {instance.uuid}: {instance.state}")
serializer = self.get_serializer(instance)
return Response(serializer.data)
# from another entity to application
class CategoryWithdrawSubmitAPIView(generics.CreateAPIView):
permission_classes = [TokenHasScope]
serializer_class = ApplicationWithdrawSerializer
required_scopes = ['wallet.application.withdraw:submit']
def get_queryset(self):
category_uuid = self.kwargs.get('category_uuid')
if category_uuid:
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
else:
category = None
application = get_application(self.request)
wallet, created = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
category=category)
return Transaction.objects.filter(application=application, wallet=wallet).all()
def perform_create(self, serializer):
# TODO: get_or_create instead of create if uuid given
# TODO: it is possible to merage create and submit in one step for improve performance
logger.info("Starting perform_create for Transaction")
application = get_application(self.request)
logger.debug(f"Application UUID: {application.uuid}")
payer_wallet_category_uuid = serializer.validated_data.pop('payer_wallet_category')
payer_wallet_category_uuid = payer_wallet_category_uuid and str(payer_wallet_category_uuid)
payee_wallet_category_uuid = self.kwargs.get('category_uuid')
if payee_wallet_category_uuid:
payee_wallet_category = get_object_or_404(Category.objects.all(), uuid=payee_wallet_category_uuid)
else:
payee_wallet_category = None
payer_wallet_category = get_object_or_404(Category.objects.all(), uuid=payer_wallet_category_uuid)
if not (payer_wallet_category and payer_wallet_category.currency == payee_wallet_category.currency):
raise UnprocessableEntity('payer and payee wallet currency not equal')
payer_id = serializer.validated_data.pop('payer')
payer_type = serializer.validated_data.pop('payer_type')
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
category=payee_wallet_category)
logger.debug(f"Payee wallet: {payee_wallet.uuid}")
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payer_id, owner_type=payer_type,
category=payer_wallet_category)
logger.debug(f"Payer wallet: {payer_wallet.uuid}")
uuid = serializer.validated_data.pop('uuid', None)
details = serializer.validated_data.get('details', {})
reference_id = details.get('reference_id', None)
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
transaction_params = {
'application': application,
'payer_wallet': payer_wallet,
'payee_wallet': payee_wallet,
'amount': serializer.validated_data.get('amount'),
}
if uuid:
transaction_params['uuid'] = uuid
if reference_id:
transaction_params['details__reference_id'] = reference_id
# else:
# logger.error('Missing UUID and Reference ID')
# raise UnprocessableEntity('Missing UUID or reference_id')
instance, created = Transaction.objects.get_or_create(**transaction_params, defaults={
'details': details
})
serializer.instance = instance
logger.info(f"Transaction {'created' if created else 'retrieved'} successfully: {instance.uuid}")
if instance.state == StateChoices.PENDING:
return
# TODO: status code 200 if not created before
if created or instance.state == StateChoices.CREATED:
instance.category_submit()
else:
logger.warning(f"Transaction was not in processable state! for {instance.uuid}: {instance.state}")
raise UnprocessableEntity(_('Transaction was not in processable state!'))
class CategoryWithdrawVerifyAPIView(generics.RetrieveAPIView):
permission_classes = [TokenHasScope]
serializer_class = ApplicationWithdrawSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.application.withdraw:verify']
def get_queryset(self):
application = get_application(self.request)
category_uuid = self.kwargs.get('category_uuid')
if category_uuid:
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
else:
category = None
wallet = Wallet.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, category=category)
return Transaction.objects.filter(application=application, payee_wallet=wallet).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
if instance.state == StateChoices.PENDING:
instance.category_verify()
serializer = self.get_serializer(instance)
return Response(serializer.data)