Skip to content

Commit

Permalink
Merge pull request #15 from mohamad-liyaghi/transactions-logic
Browse files Browse the repository at this point in the history
Transactions logic
  • Loading branch information
mohamad-liyaghi authored Aug 9, 2024
2 parents fe7e13c + c24e725 commit b3bd570
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 23 deletions.
17 changes: 12 additions & 5 deletions apps/orders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from django.conf import settings
from django.db.models import QuerySet
from uuid import uuid4
from orders.exceptions import EmptyCartException, InsufficientBalanceException
from orders.exceptions import EmptyCartException
from orders.enums import OrderStatus
from transactions.models import Transaction
from restaurants.models import Restaurant
from products.models import Product

Expand All @@ -25,10 +26,11 @@ def __str__(self):

def save(self, *args, **kwargs):
if self.status == OrderStatus.PROCESSING and not self.is_removed_from_balance:
if self.user.balance < self.total_price:
raise InsufficientBalanceException
self.user.balance -= self.total_price
self.user.save()
Transaction.transfer(
sender=self.user,
receiver=self.restaurant.owner,
amount=self.total_price,
)
return super().save(*args, **kwargs)

@classmethod
Expand Down Expand Up @@ -64,6 +66,11 @@ def create_order(cls, user: settings.AUTH_USER_MODEL, cart: dict) -> QuerySet:

with transaction.atomic():
OrderItem.objects.bulk_create(order_items)

for item in order_items:
item.product.quantity -= item.quantity
item.product.save()

for order in orders.values():
order.save()

Expand Down
4 changes: 2 additions & 2 deletions apps/orders/views/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework import status
from drf_spectacular.utils import extend_schema_view, OpenApiResponse, extend_schema
from orders.enums import OrderStatus
from orders.exceptions import InsufficientBalanceException
from transactions.exceptions import InsufficientBalanceError
from orders.models import Order
from orders.serializers import OrderSerializer

Expand Down Expand Up @@ -80,7 +80,7 @@ def post(self, request, *args, **kwargs):
order.save()
return Response({"message": "Order is being processed"}, status=status.HTTP_200_OK)

except InsufficientBalanceException:
except InsufficientBalanceError:
return Response({"error": "Insufficient balance"}, status=status.HTTP_400_BAD_REQUEST)


Expand Down
2 changes: 1 addition & 1 deletion apps/products/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get_queryset(self):
restaurant = self._get_restaurant()
return Product.objects.select_related("restaurant", "restaurant__owner").filter(
restaurant=restaurant, is_deleted=False
) # TODO: make this single query
)

def _get_restaurant(self, for_creation=False):
if not for_creation:
Expand Down
2 changes: 2 additions & 0 deletions apps/transactions/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class InsufficientBalanceError(Exception):
pass
55 changes: 42 additions & 13 deletions apps/transactions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from decimal import Decimal
from uuid import uuid4
from transactions.enums import TransactionType, TransactionStatus
from transactions.exceptions import InsufficientBalanceError
from transactions.tasks import do_withdraw


Expand All @@ -21,18 +22,46 @@ def __str__(self):

def save(self, *args, **kwargs):
if self.status == TransactionStatus.SUCCESS and not self.is_processed:
if self.type == TransactionType.DEPOSIT:
self._handle_deposit()
elif self.type == TransactionType.WITHDRAWAL:
self._handle_withdrawal()
self.is_processed = True
return super().save(*args, **kwargs)

def _handle_deposit(self):
self.user.balance += Decimal(self.amount)
self.user.save()
self._process_transaction()
super().save(*args, **kwargs)

def _process_transaction(self):
amount = Decimal(self.amount)

if self.type == TransactionType.DEPOSIT:
self._adjust_balance(amount)
elif self.type in {TransactionType.WITHDRAWAL, TransactionType.COST}:
self._adjust_balance(-amount)
elif self.type == TransactionType.CHARGE:
self._adjust_balance(amount)

self.is_processed = True

def _handle_withdrawal(self):
self.user.balance -= Decimal(self.amount)
if self.type == TransactionType.WITHDRAWAL:
do_withdraw.delay(self.user.id, self.id)

def _adjust_balance(self, amount):
if amount < 0 and self.user.balance < abs(amount):
raise InsufficientBalanceError
self.user.balance += amount
self.user.save()
do_withdraw.delay(self.user.id, self.id)

@classmethod
def transfer(cls, sender, receiver, amount) -> tuple:
"""Transfer money from sender to receiver."""
if sender.balance < amount:
raise InsufficientBalanceError

sender_transaction = cls.objects.create(
user=sender,
amount=amount,
type=TransactionType.COST,
status=TransactionStatus.SUCCESS,
)
receiver_transaction = cls.objects.create(
user=receiver,
amount=amount,
type=TransactionType.CHARGE,
status=TransactionStatus.SUCCESS,
)
return sender_transaction, receiver_transaction
10 changes: 10 additions & 0 deletions apps/transactions/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ def test_user_balance_decreased_after_successful_withdrawal(self, user):
status=TransactionStatus.SUCCESS,
)
assert user.balance == user_balance - Decimal(successful_withdrawal.amount)

def test_transfer_money_between_users(self, user, another_user):
user_balance = user.balance
another_user_balance = another_user.balance
amount = Decimal(100.00)
Transaction.transfer(user, another_user, amount)
user.refresh_from_db()
another_user.refresh_from_db()
assert user.balance == user_balance - amount
assert another_user.balance == another_user_balance + amount
1 change: 0 additions & 1 deletion apps/users/tests/test_views/test_profile/test_retrieve.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ def test_retrieve_authorized_succeeds(self, user):
assert response.data["email"] == user.email
assert response.data["first_name"] == user.first_name
assert response.data["last_name"] == user.last_name
assert Decimal(response.data["balance"]) == Decimal(user.balance)
1 change: 0 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ COPY . /src/
WORKDIR /src

EXPOSE 8000
# TODO: This Dockerfile is shit, clean it up

0 comments on commit b3bd570

Please sign in to comment.