409 lines
18 KiB
Python
Executable file
409 lines
18 KiB
Python
Executable file
import logging
|
|
|
|
from django.db.models import Q
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
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
|
|
import uuid
|
|
|
|
from apps.users.models import User
|
|
from apps.wallet.constans import TypeChoices, StateChoices
|
|
from apps.wallet.filters import AccountFilter, UserTransactionFilter
|
|
from apps.wallet.models import Transaction, Wallet, Account
|
|
from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \
|
|
TransactionSerializer, AccountSerializer
|
|
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
|
|
|
|
|
|
class AccountListAPIView(generics.ListAPIView):
|
|
'''
|
|
API for getting specified user or application balance.
|
|
'''
|
|
serializer_class = AccountSerializer
|
|
filter_backends = (DjangoFilterBackend,)
|
|
filterset_class = AccountFilter
|
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
required_scopes = ['wallet.application:get_balance']
|
|
|
|
def get_object(self):
|
|
wallet_uuid = self.kwargs.get('wallet')
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
|
application = get_application(self.request)
|
|
account, created = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
wallet=wallet)
|
|
return account
|
|
|
|
def get_queryset(self):
|
|
application = get_application(self.request)
|
|
return Account.objects.filter(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION).all()
|
|
|
|
|
|
class UserAccountListAPIView(generics.ListAPIView):
|
|
'''
|
|
API for getting specified user wallet.
|
|
'''
|
|
serializer_class = AccountSerializer
|
|
filter_backends = (DjangoFilterBackend,)
|
|
filterset_class = AccountFilter
|
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
required_scopes = ['wallet.application:get_user_balance']
|
|
|
|
queryset = Account.objects.all()
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
owner_uuid = self.kwargs.get('owner')
|
|
user, created = User.objects.get_or_create(uuid=self.kwargs.get('uuid'))
|
|
return qs.filter(owner_uuid=owner_uuid, owner_type=TypeChoices.USER).all()
|
|
|
|
|
|
class TransactionDetailAPIView(generics.RetrieveAPIView):
|
|
serializer_class = TransactionSerializer
|
|
lookup_field = 'uuid'
|
|
filter_backends = (DjangoFilterBackend,)
|
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
required_scopes = ['wallet.application:get_transaction_detail']
|
|
queryset = Transaction.objects.all()
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
|
|
application = get_application(self.request)
|
|
return Transaction.objects.filter(Q(application=application) |
|
|
Q(payer_account__owner_uuid=application.pk,
|
|
payer_account__owner_type=TypeChoices.APPLICATION,
|
|
) |
|
|
Q(payee_account__owner_uuid=application.pk,
|
|
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
)
|
|
).all()
|
|
|
|
|
|
class TransactionListAPIView(generics.ListAPIView):
|
|
serializer_class = TransactionSerializer
|
|
lookup_field = 'uuid'
|
|
filter_backends = (DjangoFilterBackend,)
|
|
filterset_class = UserTransactionFilter
|
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
required_scopes = ['wallet.application:get_transaction_list']
|
|
queryset = Transaction.objects.all()
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
|
|
application = get_application(self.request)
|
|
return qs.filter(Q(application=application) |
|
|
Q(payer_account__owner_uuid=application.pk,
|
|
payer_account__owner_type=TypeChoices.APPLICATION) |
|
|
Q(payee_account__owner_uuid=application.pk,
|
|
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
)
|
|
).all()
|
|
|
|
class WalletTransactionDetailAPIView(generics.RetrieveAPIView):
|
|
permission_classes = [TokenHasScope]
|
|
serializer_class = TransactionSerializer
|
|
lookup_field = 'uuid'
|
|
required_scopes = ['wallet.application:get_transaction_detail']
|
|
queryset = Transaction.objects.all()
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
|
|
wallet_uuid = self.kwargs.get('wallet_uuid')
|
|
if wallet_uuid:
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
|
else:
|
|
wallet = None
|
|
|
|
application = get_application(self.request)
|
|
return Transaction.objects.filter(Q(application=application) |
|
|
Q(payer_account__owner_uuid=application.pk,
|
|
payer_account__owner_type=TypeChoices.APPLICATION,
|
|
payer_account__wallet=wallet) |
|
|
Q(payee_account__owner_uuid=application.pk,
|
|
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
payee_account__wallet=wallet)
|
|
).all()
|
|
|
|
|
|
class WalletTransactionListAPIView(generics.ListAPIView):
|
|
permission_classes = [TokenHasScope]
|
|
serializer_class = TransactionSerializer
|
|
lookup_field = 'uuid'
|
|
required_scopes = ['wallet.application:get_transaction_list']
|
|
queryset = Transaction.objects.all()
|
|
|
|
def get_queryset(self):
|
|
qs = super().get_queryset()
|
|
|
|
wallet_uuid = self.kwargs.get('wallet_uuid')
|
|
if wallet_uuid:
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
|
else:
|
|
wallet = None
|
|
|
|
application = get_application(self.request)
|
|
return qs.filter(Q(application=application) |
|
|
Q(payer_account__owner_uuid=application.pk,
|
|
payer_account__owner_type=TypeChoices.APPLICATION,
|
|
payer_account__wallet=wallet) |
|
|
Q(payee_account__owner_uuid=application.pk,
|
|
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
payee_account__wallet=wallet)
|
|
).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):
|
|
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
application = get_application(self.request)
|
|
payer_account, created = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
wallet=wallet)
|
|
return Transaction.objects.filter(application=application, payer_account=payer_account).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_uuid = self.kwargs.get('payer_wallet')
|
|
if payer_wallet_uuid:
|
|
payer_wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
else:
|
|
payer_wallet = None
|
|
payee_wallet_uuid = serializer.validated_data.pop('payee_wallet')
|
|
payee_wallet_uuid = payee_wallet_uuid and str(payee_wallet_uuid)
|
|
payee_wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
if not (payee_wallet and payee_wallet.currency == payer_wallet.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_account, _ = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
wallet=payer_wallet)
|
|
logger.debug(f"Payer account: {payer_account.uuid}")
|
|
payee_account, _ = Account.objects.get_or_create(owner_uuid=payee_id, owner_type=payee_type,
|
|
wallet=payee_wallet)
|
|
logger.debug(f"Payee account: {payee_account.uuid}")
|
|
uuid_param = serializer.validated_data.pop('uuid', uuid.uuid4()) # if uuid not provided generate new one to handle get_or_create
|
|
|
|
details = serializer.validated_data.get('details', {})
|
|
reference_id = details.get('reference_id', None)
|
|
logger.debug(f"Transaction UUID: {uuid_param}, Reference ID: {reference_id}")
|
|
transaction_params = {
|
|
'uuid': uuid_param,
|
|
'application': application,
|
|
'payer_account': payer_account,
|
|
'payee_account': payee_account,
|
|
'amount': serializer.validated_data.get('amount'),
|
|
}
|
|
# TODO: checking reference_id
|
|
# if reference_id:
|
|
# transaction_params['details__reference_id'] = 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
|
|
|
|
if created or instance.state == StateChoices.CREATED:
|
|
instance.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 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)
|
|
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
|
if payer_wallet_uuid:
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
else:
|
|
wallet = None
|
|
account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
|
return Transaction.objects.filter(application=application, payer_account=account).all()
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
instance = self.get_object()
|
|
if instance.state == StateChoices.PENDING:
|
|
instance.verify()
|
|
logger.warning(f"Transaction was verified for {instance.uuid}: {instance.state}")
|
|
serializer = self.get_serializer(instance)
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
class DepositRollbackAPIView(generics.RetrieveAPIView):
|
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
serializer_class = ApplicationDepositSerializer
|
|
lookup_field = 'uuid'
|
|
required_scopes = ['wallet.application.deposit:rollback']
|
|
|
|
def get_queryset(self):
|
|
application = get_application(self.request)
|
|
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
|
if payer_wallet_uuid:
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
else:
|
|
wallet = None
|
|
account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
|
return Transaction.objects.filter(application=application, payer_account=account).all()
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
instance = self.get_object()
|
|
|
|
instance.rollback()
|
|
logger.warning(f"Transaction was rolled back for {instance.uuid}: {instance.state}")
|
|
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):
|
|
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
application = get_application(self.request)
|
|
payee_account, created = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
wallet=wallet)
|
|
return Transaction.objects.filter(application=application, payee_account=payee_account).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_uuid = serializer.validated_data.pop('payer_wallet')
|
|
payer_wallet_uuid = payer_wallet_uuid and str(payer_wallet_uuid)
|
|
|
|
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
|
|
|
payee_wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
|
|
payer_wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
|
|
if not (payer_wallet and payer_wallet.currency == payee_wallet.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_account, _ = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
wallet=payee_wallet)
|
|
logger.debug(f"Payee account: {payee_account.uuid}")
|
|
payer_account, _ = Account.objects.get_or_create(owner_uuid=payer_id, owner_type=payer_type,
|
|
wallet=payer_wallet)
|
|
logger.debug(f"Payer account: {payer_account.uuid}")
|
|
|
|
uuid_param = serializer.validated_data.pop('uuid', uuid.uuid4()) # if uuid not provided generate new one to handle get_or_create
|
|
|
|
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 = {
|
|
'uuid': uuid_param,
|
|
'application': application,
|
|
'payer_account': payer_account,
|
|
'payee_account': payee_account,
|
|
'amount': serializer.validated_data.get('amount'),
|
|
}
|
|
|
|
# TODO: checking reference_id
|
|
# if reference_id:
|
|
# transaction_params['details__reference_id'] = 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
|
|
|
|
if created or instance.state == StateChoices.CREATED:
|
|
instance.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 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)
|
|
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
payee_account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
|
return Transaction.objects.filter(application=application, payee_account=payee_account).all()
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
instance = self.get_object()
|
|
if instance.state == StateChoices.PENDING:
|
|
instance.verify()
|
|
|
|
serializer = self.get_serializer(instance)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class WithdrawRollbackAPIView(generics.RetrieveAPIView):
|
|
permission_classes = [TokenHasScope]
|
|
serializer_class = ApplicationWithdrawSerializer
|
|
lookup_field = 'uuid'
|
|
required_scopes = ['wallet.application.withdraw:rollback']
|
|
|
|
def get_queryset(self):
|
|
application = get_application(self.request)
|
|
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
payee_account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
|
return Transaction.objects.filter(application=application, payee_account=payee_account).all()
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
instance = self.get_object()
|
|
|
|
instance.rollback()
|
|
logger.warning(f"Transaction was rolled back for {instance.uuid}: {instance.state}")
|
|
serializer = self.get_serializer(instance)
|
|
return Response(serializer.data)
|