diff --git a/apps/transactions/migrations/0001_initial.py b/apps/transactions/migrations/0001_initial.py index 9c8185d..9b2f357 100644 --- a/apps/transactions/migrations/0001_initial.py +++ b/apps/transactions/migrations/0001_initial.py @@ -19,7 +19,7 @@ class Migration(migrations.Migration): ('code', models.IntegerField(db_index=True, unique=True, verbose_name='code')), ('amount', models.IntegerField(verbose_name='amount')), ('delay', models.IntegerField(blank=True, default=0)), - ('state', models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'failed'), (5, 'done')], default=1)), + ('state', models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'failed'), (5, 'success')], default=1)), ('create', models.DateTimeField(auto_now=True, null=True, verbose_name='create')), ('last_update', models.DateTimeField(auto_now=True, null=True, verbose_name='last update')), ('application_id', models.IntegerField(blank=True, null=True, verbose_name='application ID')), diff --git a/apps/transactions/models.py b/apps/transactions/models.py index 3619c76..20c37a2 100755 --- a/apps/transactions/models.py +++ b/apps/transactions/models.py @@ -8,18 +8,19 @@ 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): + def create_user_transaction(self, + amount, + payee: User, + payer: User = None, + delay=0, + detail_id=None, + application_client_id=None, + state=None): if not (isinstance(payer, User) or payer is None): raise ValidationError('payer type not valid') if not isinstance(payee, User): @@ -42,16 +43,42 @@ class TransactionManager(models.Manager): transaction.save() return transaction + def create_service_transaction( + self, + amount, + payee: User = None, + payer: User = None, + # delay=0, + detail_id=None, + application_client_id=None, + # state=None + ): + if not (isinstance(payer, User) or payer is None): + raise ValidationError('payer type not valid') + if not (isinstance(payee, User) or payee is None): + 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( + payer=payer, + payee=payee, + amount=amount, + # delay=delay, + detail_id=detail_id, + application_client_id=application_client_id, + state=STATE_CHOICES.pending + ) + + transaction.save() + return transaction + class Transaction(models.Model): - STATE_CHOICES = Choices( - (1, 'created', 'created'), - (2, 'delayed', 'delayed'), - (3, 'pending', 'pending'), - (4, 'failed', 'failed'), - (5, 'done', 'done'), - ) - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) # reserve_code + 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) @@ -81,7 +108,7 @@ class Transaction(models.Model): # TODO: check bundry conditions self.last_update = datetime.datetime.now() - if not self.state == self.STATE_CHOICES.created: + if not self.state == STATE_CHOICES.created: raise APIException('this transaction is not ready to pay') if self.payer.balance < self.amount: @@ -95,14 +122,14 @@ class Transaction(models.Model): raise APIException('not enough balance') if self.delay: - self.state = self.STATE_CHOICES.delayed + self.state = STATE_CHOICES.delayed self.save() else: self.payee.balance = F('balance') + self.amount self.payee.save(update_fields=['balance']) - self.state = self.STATE_CHOICES.done + self.state = STATE_CHOICES.success self.save() self.save() @@ -112,11 +139,49 @@ class Transaction(models.Model): with transaction.atomic(): self.last_update = datetime.datetime.now() # TODO: check bundry conditions - if not self.state == self.STATE_CHOICES.delayed: + if not self.state == STATE_CHOICES.delayed: raise APIException('this transaction is not delayed') self.payee.balance = F('balance') + self.amount - self.state = self.STATE_CHOICES.done + self.state = STATE_CHOICES.success + self.payee.save() + self.save() + + def submit(self): + with transaction.atomic(): + # TODO: check bundry conditions + self.last_update = datetime.datetime.now() + + if not self.state == STATE_CHOICES.created: + raise APIException('this transaction is not ready to pay') + + if self.payer: + 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') + + 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 verify(self): + with transaction.atomic(): + self.last_update = datetime.datetime.now() + # TODO: check bundry conditions + if not self.state == STATE_CHOICES.pending: + raise APIException('this transaction is not pending') + + self.payee.balance = F('balance') + self.amount + self.state = STATE_CHOICES.success self.payee.save() self.save() diff --git a/apps/transactions/serializers.py b/apps/transactions/serializers.py index e458152..307083e 100755 --- a/apps/transactions/serializers.py +++ b/apps/transactions/serializers.py @@ -34,10 +34,10 @@ class TransactionSerializer(serializers.ModelSerializer): if user == payer: with atomic(): - transaction = Transaction.objects.create_transaction(**validated_data) + transaction = Transaction.objects.create_user_transaction(**validated_data) transaction.pay() else: - transaction = Transaction.objects.create_transaction(**validated_data) + transaction = Transaction.objects.create_user_transaction(**validated_data) return transaction diff --git a/apps/users/constans.py b/apps/users/constans.py index f515529..d4ea889 100644 --- a/apps/users/constans.py +++ b/apps/users/constans.py @@ -1,2 +1,14 @@ +from model_utils.choices import Choices + MAX_OTP_TRY = 3 -DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998'] \ No newline at end of file +DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998'] + +STATE_CHOICES = Choices( + (1, 'created', 'created'), + (2, 'delayed', 'delayed'), # delayed as user wish + (3, 'pending', 'pending'), # wait for external service response + (4, 'incomplete', 'incomplete'), # started and wait for internal progress to complete + (5, 'success', 'success'), + (6, 'failed', 'failed'), + (7, 'expected_failure', 'incomplete'), # no exact data available but guessed to be failed + ) \ No newline at end of file diff --git a/client.py b/client.py index c539167..b634a58 100644 --- a/client.py +++ b/client.py @@ -128,7 +128,7 @@ class ApiClient(): path = f'transactions/{uuid}' return self._request(path=path) - def create_transaction(self, amount, delay, payer=None): + def create_user_transaction(self, amount, delay, payer=None): data = { 'delay': delay, 'amount': amount