new change state

This commit is contained in:
Sayyid Hamid Mahdavi 2025-12-27 14:53:45 +03:30
parent dae2a2e518
commit bd3438528d

View file

@ -5,6 +5,7 @@ from django.core.validators import MinValueValidator
from django.db import models
from django.db import transaction
from django.db.models import F
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import APIException
from django.conf import settings
@ -20,6 +21,7 @@ from ..gooyal_oauth2.models import Application
logger = logging.getLogger(__name__)
class CurrencyChoices(models.TextChoices):
IRANIAN_RIAL = '166ab7ce33b34799950d45035a9997d6', _('IRANIAN RIAL') # uuid without dash
@ -27,6 +29,7 @@ class CurrencyChoices(models.TextChoices):
class AccountQuerySet(models.QuerySet):
pass
class Account(BaseModel):
owner_uuid = models.UUIDField(db_index=True, null=False)
wallet = models.ForeignKey('Wallet', on_delete=models.PROTECT, null=True)
@ -50,6 +53,7 @@ class Account(BaseModel):
class WalletQuerySet(models.QuerySet):
pass
class Wallet(BaseModel):
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, null=True)
label = models.CharField(_('label'), max_length=255)
@ -153,18 +157,26 @@ class Transaction(BaseModel):
def payee_type(self):
return self.payee_account.owner_type
def change_state(self, from_state, to_state, error_message=None, **data):
if self.state == from_state:
updated = Transaction.objects.filter(pk=self.pk, state=from_state).update(
def change_state(self, from_states: list | tuple, to_state, same_ok=False, raise_exception=True,
error_message=None,
data: None | dict = None):
data = data or {}
data['updated_at'] = timezone.now()
if same_ok and self.state == to_state:
return self
if self.state in from_states:
updated = self.__class__.objects.filter(pk=self.pk, state__in=from_states).update(
state=to_state,
**data
)
if updated: # double check in db level
self.state = to_state
else:
raise APIException(error_message)
else:
raise APIException(error_message)
return self
if raise_exception:
raise Exception(error_message)
def withdraw_from_payer_balance(self):
logger.debug('start withdraw_from_payer_balance')
@ -186,14 +198,12 @@ class Transaction(BaseModel):
else:
raise UnprocessableEntity('not enough balance')
def rollback_withdraw_to_payer_balance(self):
logger.debug('start rollback deposit_to_payee_balance')
logger.info(f'payer_account balance {self.payer_account} -> {self.amount}')
self.payer_account.balance = F('balance') + self.amount
self.payer_account.save(update_fields=['balance'])
def deposit_to_payee_balance(self):
logger.debug('start deposit_to_payee_balance')
logger.info(f'payee_account balance {self.payee_account} -> {self.payer_account.balance} + {self.amount}')
@ -223,20 +233,24 @@ class Transaction(BaseModel):
with transaction.atomic():
if self.delay:
self.change_state(
StateChoices.CREATED,
[StateChoices.CREATED],
StateChoices.DELAYED,
error_message='this transaction is not ready to pay',
payer_id=payer_id,
payer_type=payer_type
data={
"payer_id": payer_id,
"payer_type": payer_type
}
)
else:
self.change_state(
StateChoices.CREATED,
[StateChoices.CREATED],
StateChoices.SUCCESS,
error_message='this transaction is not ready to pay',
payer_id=payer_id,
payer_type=payer_type
data={
"payer_id": payer_id,
"payer_type": payer_type
}
)
self.deposit_to_payee_balance()
@ -250,7 +264,7 @@ class Transaction(BaseModel):
with transaction.atomic():
self.change_state(
StateChoices.DELAYED,
[StateChoices.DELAYED],
StateChoices.SUCCESS,
error_message='this transaction is not delayed'
)
@ -265,7 +279,7 @@ class Transaction(BaseModel):
raise APIException('payee identification error')
if not self.payer_account.wallet.currency == self.payee_account.wallet.currency:
self.change_state(from_state=self.state, to_state=StateChoices.FAILED)
self.change_state([self.state], StateChoices.FAILED)
logger.info(f'payer({self.payer_account.wallet.currency}) and payee({self.payee_account.wallet.currency}) account currency not equal')
raise APIException('payer and payee account currency not equal')
@ -276,7 +290,7 @@ class Transaction(BaseModel):
with transaction.atomic():
self.withdraw_from_payer_balance()
self.change_state(
StateChoices.CREATED,
[StateChoices.CREATED],
StateChoices.PENDING,
error_message=_('this transaction is not ready to pay')
)
@ -289,7 +303,7 @@ class Transaction(BaseModel):
with transaction.atomic():
self.deposit_to_payee_balance()
self.change_state(
StateChoices.PENDING,
[StateChoices.PENDING],
StateChoices.SUCCESS,
error_message=_('this transaction is not pending')
)
@ -302,7 +316,7 @@ class Transaction(BaseModel):
with transaction.atomic():
self.rollback_withdraw_to_payer_balance()
self.change_state(
self.state,
[StateChoices.PENDING, StateChoices.DELAYED, StateChoices.INCOMPLETE],
StateChoices.SUCCESS,
error_message=_('TRANSACTION_STATE_NOT_SUITABLE_FOR_ROLLBACK')
)