wallet/apps/transactions/models.py

164 lines
5.6 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 model_utils.choices import Choices
from rest_framework.exceptions import APIException, ValidationError
from apps.users.constans import STATE_CHOICES
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_client_id=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_client_id=application_client_id
)
# 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=STATE_CHOICES, default=STATE_CHOICES.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_client_id = models.CharField(max_length=100, db_index=True, null=True)
detail_id = models.IntegerField(null=True, blank=True) # TODO: or better detail url
objects = TransactionManager()
# class Meta:
# unique_together = ('application_client_id', '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 == STATE_CHOICES.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 = STATE_CHOICES.delayed
self.save()
else:
self.payee.balance = F('balance') + self.amount
self.payee.save(update_fields=['balance'])
self.state = STATE_CHOICES.success
self.save()
self.save()
return self
def receipt(self):
# TODO: check bundry conditions
if not self.state == STATE_CHOICES.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 = STATE_CHOICES.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 == STATE_CHOICES.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 = STATE_CHOICES.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 == STATE_CHOICES.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 = STATE_CHOICES.success
self.payee.save()
self.save()
def __str__(self):
return f"{self.payer} -> {self.payee} ({self.uuid}): {self.amount}"