165 lines
5.6 KiB
Python
Executable file
165 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 .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):
|
|
state_choices = STATE_CHOICES
|
|
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}"
|