Merge branch 'develop' of git.mindplus-dev.ir:sahama/wallet into FIX/fix-deposit-submit-and-set-logger
# Conflicts: # apps/wallet/serializers.py # apps/wallet/views/application.py
This commit is contained in:
commit
0bc2906239
5 changed files with 87 additions and 18 deletions
18
apps/wallet/migrations/0011_category_currency.py
Normal file
18
apps/wallet/migrations/0011_category_currency.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.1.7 on 2025-10-11 13:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wallet', '0010_rename_payee_id_transaction__payee_id_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='currency',
|
||||||
|
field=models.UUIDField(choices=[('166ab7ce33b34799950d45035a9997d6', 'IRANIAN RIAL')], db_index=True, default='166ab7ce33b34799950d45035a9997d6'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -3,10 +3,11 @@ from django.db import models
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.exceptions import APIException, NotAcceptable
|
from rest_framework.exceptions import APIException
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from oauth2_provider import settings as oauth2_settings
|
from oauth2_provider import settings as oauth2_settings
|
||||||
|
|
||||||
|
from utils.exceptions import UnprocessableEntity
|
||||||
from gooyal_accounts_client.models import PublicUser
|
from gooyal_accounts_client.models import PublicUser
|
||||||
from utils.accounts_client import get_user_details, get_application_details
|
from utils.accounts_client import get_user_details, get_application_details
|
||||||
from utils.models import BaseModel
|
from utils.models import BaseModel
|
||||||
|
|
@ -15,6 +16,10 @@ from apps.users.models import User
|
||||||
from ..gooyal_oauth2.models import Application
|
from ..gooyal_oauth2.models import Application
|
||||||
|
|
||||||
|
|
||||||
|
class CurrencyChoices(models.TextChoices):
|
||||||
|
IRANIAN_RIAL = '166ab7ce33b34799950d45035a9997d6', _('IRANIAN RIAL') # uuid without dash
|
||||||
|
|
||||||
|
|
||||||
class WalletQuerySet(models.QuerySet):
|
class WalletQuerySet(models.QuerySet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -40,8 +45,8 @@ class CategoryQuerySet(models.QuerySet):
|
||||||
|
|
||||||
class Category(BaseModel):
|
class Category(BaseModel):
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, null=True)
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, null=True)
|
||||||
|
|
||||||
label = models.CharField(_('label'), max_length=255)
|
label = models.CharField(_('label'), max_length=255)
|
||||||
|
currency = models.UUIDField(choices=CurrencyChoices.choices, db_index=True, default=CurrencyChoices.IRANIAN_RIAL)
|
||||||
|
|
||||||
objects = CategoryQuerySet.as_manager()
|
objects = CategoryQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -120,6 +125,14 @@ class Transaction(BaseModel):
|
||||||
def get_payee(self):
|
def get_payee(self):
|
||||||
return self.payee_wallet.get_owner()
|
return self.payee_wallet.get_owner()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def payer_wallet_category(self):
|
||||||
|
return self.payer_wallet and self.payer_wallet.category_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def payee_wallet_category(self):
|
||||||
|
return self.payee_wallet and self.payee_wallet.category_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def payer_id(self):
|
def payer_id(self):
|
||||||
return self.payer_wallet.owner_uuid
|
return self.payer_wallet.owner_uuid
|
||||||
|
|
@ -154,7 +167,7 @@ class Transaction(BaseModel):
|
||||||
payer_is_allowed_negative_balance = self.payer_wallet.owner_type == TypeChoices.APPLICATION.value and str(
|
payer_is_allowed_negative_balance = self.payer_wallet.owner_type == TypeChoices.APPLICATION.value and str(
|
||||||
self.payer_wallet.owner_uuid) in settings.ALLOWED_NEGATIVE_BALANCE_APPLICATIONS
|
self.payer_wallet.owner_uuid) in settings.ALLOWED_NEGATIVE_BALANCE_APPLICATIONS
|
||||||
if self.amount > self.payer_wallet.balance and not payer_is_allowed_negative_balance:
|
if self.amount > self.payer_wallet.balance and not payer_is_allowed_negative_balance:
|
||||||
raise APIException('not enough balance')
|
raise UnprocessableEntity('not enough balance')
|
||||||
self.payer_wallet.balance = F('balance') - self.amount
|
self.payer_wallet.balance = F('balance') - self.amount
|
||||||
self.payer_wallet.save(update_fields=['balance'])
|
self.payer_wallet.save(update_fields=['balance'])
|
||||||
|
|
||||||
|
|
@ -224,6 +237,10 @@ class Transaction(BaseModel):
|
||||||
if not self.payee_wallet:
|
if not self.payee_wallet:
|
||||||
raise APIException('payee identification error')
|
raise APIException('payee identification error')
|
||||||
|
|
||||||
|
if not self.payer_wallet.category == self.payee_wallet.category:
|
||||||
|
self.change_state(from_state=self.state, to_state=StateChoices.FAILED)
|
||||||
|
raise APIException('payer and payee wallet currency not equal')
|
||||||
|
|
||||||
if not self.state == StateChoices.CREATED:
|
if not self.state == StateChoices.CREATED:
|
||||||
raise APIException('this transaction is not ready to pay')
|
raise APIException('this transaction is not ready to pay')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ class ApplicationDepositSerializer(serializers.ModelSerializer):
|
||||||
uuid = serializers.UUIDField(required=False, validators=[])
|
uuid = serializers.UUIDField(required=False, validators=[])
|
||||||
payee_id = serializers.UUIDField(required=True)
|
payee_id = serializers.UUIDField(required=True)
|
||||||
payee_type = serializers.IntegerField(required=True)
|
payee_type = serializers.IntegerField(required=True)
|
||||||
|
payee_wallet_category = serializers.UUIDField(required=False) # TODO: required True
|
||||||
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
||||||
# TODO: details as fields
|
# TODO: details as fields
|
||||||
|
|
||||||
|
|
@ -186,8 +187,10 @@ class ApplicationDepositSerializer(serializers.ModelSerializer):
|
||||||
'uuid',
|
'uuid',
|
||||||
'payer_id',
|
'payer_id',
|
||||||
'payer_type',
|
'payer_type',
|
||||||
|
'payer_wallet_category',
|
||||||
'payee_id',
|
'payee_id',
|
||||||
'payee_type',
|
'payee_type',
|
||||||
|
'payee_wallet_category',
|
||||||
"created_at",
|
"created_at",
|
||||||
"state",
|
"state",
|
||||||
'amount',
|
'amount',
|
||||||
|
|
@ -196,12 +199,14 @@ class ApplicationDepositSerializer(serializers.ModelSerializer):
|
||||||
'details',
|
'details',
|
||||||
)
|
)
|
||||||
|
|
||||||
read_only_fields = ['updated_at', 'state', 'created_at', 'payer_id', 'payer_type']
|
read_only_fields = ['updated_at', 'state', 'created_at', 'payer_id', 'payer_type', 'payer_wallet_category']
|
||||||
|
|
||||||
|
|
||||||
class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
||||||
|
uuid = serializers.UUIDField(required=False, validators=[])
|
||||||
payer_id = serializers.UUIDField(required=True)
|
payer_id = serializers.UUIDField(required=True)
|
||||||
payer_type = serializers.IntegerField(required=True)
|
payer_type = serializers.IntegerField(required=True)
|
||||||
|
payer_wallet_category = serializers.UUIDField(required=False) # TODO: required True
|
||||||
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -210,8 +215,10 @@ class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
||||||
'uuid',
|
'uuid',
|
||||||
'payer_id',
|
'payer_id',
|
||||||
'payer_type',
|
'payer_type',
|
||||||
|
'payer_wallet_category',
|
||||||
'payee_id',
|
'payee_id',
|
||||||
'payee_type',
|
'payee_type',
|
||||||
|
'payee_wallet_category',
|
||||||
"created_at",
|
"created_at",
|
||||||
"state",
|
"state",
|
||||||
'amount',
|
'amount',
|
||||||
|
|
@ -220,4 +227,4 @@ class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
||||||
'details',
|
'details',
|
||||||
)
|
)
|
||||||
|
|
||||||
read_only_fields = ['updated_at', 'state', 'created_at', 'payee_id', 'payee_type']
|
read_only_fields = ['updated_at', 'state', 'created_at', 'payee_id', 'payee_type', 'payee_wallet_category']
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from apps.wallet.constans import TypeChoices, StateChoices
|
||||||
from apps.wallet.models import Transaction, Category, Wallet
|
from apps.wallet.models import Transaction, Category, Wallet
|
||||||
from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \
|
from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \
|
||||||
ApplicationTransactionSerializer, ApplicationBalanceSerializer, MyWalletSerializer
|
ApplicationTransactionSerializer, ApplicationBalanceSerializer, MyWalletSerializer
|
||||||
from wallet.exceptions import UnprocessableEntity
|
from utils.exceptions import UnprocessableEntity
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -281,19 +281,31 @@ class CategoryDepositSubmitAPIView(generics.CreateAPIView):
|
||||||
logger.info("Starting perform_create for Transaction")
|
logger.info("Starting perform_create for Transaction")
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
logger.debug(f"Application UUID: {application.uuid}")
|
logger.debug(f"Application UUID: {application.uuid}")
|
||||||
category_uuid = self.kwargs.get('category_uuid')
|
|
||||||
if category_uuid:
|
payee_wallet_category_uuid = serializer.validated_data.pop('payee_wallet_category')
|
||||||
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
|
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:
|
else:
|
||||||
category = None
|
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_id')
|
payee_id = serializer.validated_data.pop('payee_id')
|
||||||
payee_type = serializer.validated_data.pop('payee_type')
|
payee_type = serializer.validated_data.pop('payee_type')
|
||||||
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
||||||
category=category)
|
category=payer_wallet_category)
|
||||||
logger.debug(f"Payer wallet: {payer_wallet.uuid}")
|
logger.debug(f"Payer wallet: {payer_wallet.uuid}")
|
||||||
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payee_id, owner_type=payee_type, category=category)
|
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}")
|
logger.debug(f"Payee wallet: {payee_wallet.uuid}")
|
||||||
uuid = serializer.validated_data.pop('uuid')
|
uuid = serializer.validated_data.pop('uuid')
|
||||||
|
|
||||||
details = serializer.validated_data.get('details', {})
|
details = serializer.validated_data.get('details', {})
|
||||||
reference_id = details.get('reference_id', None)
|
reference_id = details.get('reference_id', None)
|
||||||
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
|
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
|
||||||
|
|
@ -370,16 +382,25 @@ class CategoryWithdrawSubmitAPIView(generics.CreateAPIView):
|
||||||
# TODO: get_or_create instead of create if uuid given
|
# 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
|
# TODO: it is possible to merage create and submit in one step for improve performance
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
category_uuid = self.kwargs.get('category_uuid')
|
payer_wallet_category_uuid = serializer.validated_data.pop('payer_wallet_category')
|
||||||
if category_uuid:
|
payer_wallet_category_uuid = payer_wallet_category_uuid and str(payer_wallet_category_uuid)
|
||||||
category = get_object_or_404(Category.objects.all(), uuid=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:
|
else:
|
||||||
category = None
|
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_id')
|
payer_id = serializer.validated_data.pop('payer_id')
|
||||||
payer_type = serializer.validated_data.pop('payer_type')
|
payer_type = serializer.validated_data.pop('payer_type')
|
||||||
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
||||||
category=category)
|
category=payee_wallet_category)
|
||||||
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payer_id, owner_type=payer_type, category=category)
|
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payer_id, owner_type=payer_type, category=payer_wallet_category)
|
||||||
instance = serializer.save(payer_wallet=payer_wallet, application=application, payee_wallet=payee_wallet)
|
instance = serializer.save(payer_wallet=payer_wallet, application=application, payee_wallet=payee_wallet)
|
||||||
instance.category_submit()
|
instance.category_submit()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.views import exception_handler as drf_exception_handler
|
from rest_framework.views import exception_handler as drf_exception_handler
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -52,3 +53,8 @@ class ErrorMiddleware:
|
||||||
},
|
},
|
||||||
status=500
|
status=500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class UnprocessableEntity(APIException):
|
||||||
|
status_code = 422
|
||||||
|
default_detail = _('The request was well-formed but cannot be processed due to semantic errors.')
|
||||||
|
default_code = _('unprocessable_entity')
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue