accounts/apps/wallet/models.py
2024-06-26 16:12:40 +03:30

164 lines
5.7 KiB
Python
Executable file

import datetime
import uuid
from django.db import models
from django.db import transaction
from django.db.models import F
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import APIException, ValidationError
from django.conf import settings
from .constans import StateChoices
from apps.users.models import User
class TransactionManager(models.Manager):
def create_transaction(
self,
amount,
payee: User,
payer: User = None,
delay=0,
# detail_id=None,
application=None,
# state=None,
uuid=None
):
if not (isinstance(payer, User) or payer is None):
raise ValidationError('payer type not valid')
if not isinstance(payee, User):
raise ValidationError('payee type not valid')
if not amount > 0:
raise ValidationError('amount should be greater than zero')
if not delay >= 0:
raise ValidationError('delay should be greater than equal zero')
transaction = self.model(
uuid=uuid,
payer=payer,
payee=payee,
amount=amount,
delay=delay,
# detail_id=detail_id,
application=application
)
# if state:
# transaction.state = state
transaction.save()
return transaction
class Transaction(models.Model):
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True,
db_index=True) # reserve_code
amount = models.IntegerField(_('amount'), blank=False)
delay = models.IntegerField(default=0, blank=True) # number of days for delayed payments
payer = models.ForeignKey(User, on_delete=models.PROTECT, related_name='payments', blank=True, null=True)
payee = models.ForeignKey(User, on_delete=models.PROTECT, related_name='receipts')
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
create = models.DateTimeField(_('create'), blank=True, null=True, auto_now=True)
last_update = models.DateTimeField(_('last update'), blank=True, null=True, auto_now=True)
application = models.ForeignKey(settings.OAUTH2_PROVIDER_APPLICATION_MODEL, on_delete=models.PROTECT,
related_name='transactions', null=True)
detail_id = models.IntegerField(null=True, blank=True) # TODO: or better detail url
objects = TransactionManager()
# class Meta:
# unique_together = ('application', 'detail_id',)
def pay(self, payer=None):
if not self.payer and not payer:
raise APIException('payer identification error')
if self.payer and payer and not self.payer == payer:
raise APIException('payer identification error')
# TODO: check bundry conditions
if not self.state == StateChoices.CREATED:
raise APIException('this transaction is not ready to pay')
if not self.payer:
self.payer = payer
with transaction.atomic():
self.last_update = datetime.datetime.now()
if self.payer.balance < self.amount:
raise APIException('not enough balance')
self.payer.balance = F('balance') - self.amount
self.payer.save(update_fields=['balance'])
self.payer.refresh_from_db()
if self.payer.balance < 0:
raise APIException('not enough balance')
if self.delay:
self.state = StateChoices.DELAYED
self.save()
else:
self.payee.balance = F('balance') + self.amount
self.payee.save(update_fields=['balance'])
self.state = StateChoices.SUCCESS
self.save()
self.save()
return self
def receipt(self):
# TODO: check bundry conditions
if not self.state == StateChoices.DELAYED:
raise APIException('this transaction is not delayed')
with transaction.atomic():
self.last_update = datetime.datetime.now()
self.payee.balance = F('balance') + self.amount
self.state = StateChoices.SUCCESS
self.payee.save()
self.save()
def submit(self):
if not self.payer:
raise APIException('payer identification error')
if not self.payee:
raise APIException('payee identification error')
# TODO: check bundry conditions
if not self.state == StateChoices.CREATED:
raise APIException('this transaction is not ready to pay')
with transaction.atomic():
self.last_update = datetime.datetime.now()
if self.payer.balance < self.amount:
raise APIException('not enough balance')
self.payer.balance = F('balance') - self.amount
self.payer.save(update_fields=['balance'])
self.payer.refresh_from_db()
self.state = StateChoices.PENDING
if self.payer.balance < 0:
raise APIException('not enough balance')
self.save()
return self
def verify(self):
# TODO: check bundry conditions
if not self.state == StateChoices.PENDING:
raise APIException('this transaction is not pending')
with transaction.atomic():
self.last_update = datetime.datetime.now()
self.payee.balance = F('balance') + self.amount
self.state = StateChoices.SUCCESS
self.payee.save()
self.save()
def __str__(self):
return f"{self.payer} -> {self.payee} ({self.uuid}): {self.amount}"