From 6f568125b37fff79bc2501f061671a68ba4b852f Mon Sep 17 00:00:00 2001
From: sahama
Date: Sun, 11 Aug 2024 14:55:47 +0330
Subject: [PATCH] transfer money
---
apps/gooyal_oauth2/models.py | 10 +++
apps/users/models.py | 9 ++
apps/wallet/application_views.py | 74 -----------------
apps/wallet/constans.py | 2 +-
...ion_detail_transaction_details_and_more.py | 33 ++++++++
apps/wallet/models.py | 2 +-
apps/wallet/serializers.py | 54 +++++++++++-
apps/wallet/urls.py | 38 +++++----
apps/wallet/views/__init__.py | 0
apps/wallet/views/application.py | 83 +++++++++++++++++++
apps/wallet/{views.py => views/user.py} | 14 ++--
.../include/inline_transaction_data.html | 2 +-
templates/include/top_bar.html | 2 +-
wallet/settings.py | 2 +-
wallet/urls.py | 2 -
15 files changed, 219 insertions(+), 108 deletions(-)
delete mode 100755 apps/wallet/application_views.py
create mode 100644 apps/wallet/migrations/0005_remove_transaction_detail_transaction_details_and_more.py
create mode 100644 apps/wallet/views/__init__.py
create mode 100755 apps/wallet/views/application.py
rename apps/wallet/{views.py => views/user.py} (95%)
diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py
index 1ce7ad3..d620904 100644
--- a/apps/gooyal_oauth2/models.py
+++ b/apps/gooyal_oauth2/models.py
@@ -5,6 +5,8 @@ from oauth2_provider.models import AbstractApplication, AbstractAccessToken, Abs
import uuid
from django.utils.translation import gettext_lazy as _
+from apps.wallet.constans import TypeChoices
+
class AccessToken(AbstractAccessToken):
id = None
@@ -25,6 +27,14 @@ class Application(AbstractApplication):
class Meta:
abstract = False
+ @property
+ def instance_id(self):
+ return self.pk
+
+ @property
+ def instance_type(self):
+ return TypeChoices.APPLICATION
+
class Grant(AbstractGrant):
id = None
diff --git a/apps/users/models.py b/apps/users/models.py
index e94e608..97e408b 100644
--- a/apps/users/models.py
+++ b/apps/users/models.py
@@ -12,6 +12,7 @@ import string
import base64
import hashlib
+from apps.wallet.constans import TypeChoices
class UserManager(BaseUserManager):
@@ -75,6 +76,14 @@ class User(AbstractUser):
def __str__(self):
return str(self.username or self.pk)
+ @property
+ def instance_id(self):
+ return self.pk
+
+ @property
+ def instance_type(self):
+ return TypeChoices.APPLICATION
+
# TODO: user redis instead.
# TODO: create a queryset
diff --git a/apps/wallet/application_views.py b/apps/wallet/application_views.py
deleted file mode 100755
index ede6018..0000000
--- a/apps/wallet/application_views.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from django.contrib import messages
-from django.contrib.auth.decorators import login_required
-from django.db.models import Q
-from django.urls import reverse
-from django.utils.decorators import method_decorator
-from django.views.generic import DetailView, ListView, UpdateView
-
-from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
-from rest_framework import generics, permissions, mixins
-from rest_framework.permissions import AllowAny
-from rest_framework.response import Response
-from rest_framework.viewsets import GenericViewSet, ModelViewSet
-
-from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements, \
- IsAuthenticatedOrActionMatchesOASRequirements
-from apps.wallet.models import Transaction
-from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, WalletSerializer
-from .constans import StateChoices
-from .forms import TransactionActionForm
-
-from django.utils.translation import gettext_lazy as _
-
-
-def get_application(request):
- try:
- application = request.auth.application
- except:
- application = None
- return application
-
-
-class ApplicationWalletBalanceView(generics.RetrieveAPIView):
- '''
- API for getting authenticated user balance.
- '''
- permission_classes = [TokenHasScope]
- serializer_class = WalletSerializer
- required_scopes = ['wallet.application:get_balance']
-
- def get_object(self):
- return self.request.user
-
- def retrieve(self, request, *args, **kwargs):
- return super().retrieve(request, *args, **kwargs)
-
-
-class TransactionDetail(generics.RetrieveAPIView):
- permission_classes = [permissions.IsAuthenticated, IsAuthenticatedOrTokenHasScope]
- serializer_class = TransactionSerializer
- lookup_field = 'uuid'
- required_scopes = ['wallet.transaction:retrieve']
-
- def get_queryset(self):
- user = self.request.user
- return Transaction.objects.filter(Q(payee=user) | Q(payer=user) | Q(payer__isnull=True)).all()
-
-
-class TransactionPay(generics.RetrieveAPIView):
- permission_classes = [permissions.IsAuthenticated, IsAuthenticatedOrTokenHasScope]
- serializer_class = TransactionSerializer
- lookup_field = 'uuid'
- required_scopes = ['wallet.invoice:pay']
-
- def get_queryset(self):
- user = self.request.user
- return Transaction.objects.filter(Q(payer=user) | Q(payer__isnull=True)).all()
-
- def retrieve(self, request, *args, **kwargs):
- instance = self.get_object()
- instance.pay(payer=self.request.user)
- serializer = self.get_serializer(instance)
- return Response(serializer.data)
-
-
diff --git a/apps/wallet/constans.py b/apps/wallet/constans.py
index 73d8545..3531ce6 100644
--- a/apps/wallet/constans.py
+++ b/apps/wallet/constans.py
@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
class StateChoices(IntegerChoices):
CREATED = 1 , _('created')
DELAYED = 2 , _('delayed') # delayed as user wish
- PENDING = 3 , _('pending') # wait for external service response
+ PENDING = 3 , _('pending') # wait for external service response (or action)
INCOMPLETE = 4 , _('incomplete') # started and wait for internal progress to complete
SUCCESS = 5 , _('success')
FAILED = 6 , _('failed')
diff --git a/apps/wallet/migrations/0005_remove_transaction_detail_transaction_details_and_more.py b/apps/wallet/migrations/0005_remove_transaction_detail_transaction_details_and_more.py
new file mode 100644
index 0000000..01ae594
--- /dev/null
+++ b/apps/wallet/migrations/0005_remove_transaction_detail_transaction_details_and_more.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.0.6 on 2024-08-11 10:40
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wallet', '0004_remove_transaction_payee_remove_transaction_payer_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='transaction',
+ name='detail',
+ ),
+ migrations.AddField(
+ model_name='transaction',
+ name='details',
+ field=models.JSONField(blank=True, default=dict, null=True),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='amount',
+ field=models.PositiveBigIntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='amount'),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='delay',
+ field=models.PositiveIntegerField(blank=True, default=0),
+ ),
+ ]
diff --git a/apps/wallet/models.py b/apps/wallet/models.py
index 2c68398..ceefd00 100755
--- a/apps/wallet/models.py
+++ b/apps/wallet/models.py
@@ -193,4 +193,4 @@ class Transaction(models.Model):
return self
def __str__(self):
- return f"{self.get_payer()} -> {self.get_payee()} ({self.uuid}): {self.amount}"
+ return f"{self.get_payer()} -> {self.get_payee()} ({self.uuid}): {self.amount} - {self.get_state_display()}"
diff --git a/apps/wallet/serializers.py b/apps/wallet/serializers.py
index b9cd5ae..4bfa9c1 100755
--- a/apps/wallet/serializers.py
+++ b/apps/wallet/serializers.py
@@ -3,6 +3,7 @@ from django.db.transaction import atomic
from rest_framework import serializers
from rest_framework.exceptions import APIException
+from .constans import TypeChoices
from .models import Transaction
from ..users.models import User
@@ -10,6 +11,7 @@ from ..users.models import User
class TransactionSerializer(serializers.ModelSerializer):
payee_id = serializers.UUIDField(read_only=True)
payee_type = serializers.UUIDField(read_only=True)
+
class Meta:
model = Transaction
fields = ('uuid',
@@ -92,7 +94,55 @@ class WithdrawSerializer(serializers.ModelSerializer):
return transaction
-class WalletSerializer(serializers.ModelSerializer):
+class MyWalletSerializer(serializers.ModelSerializer):
class Meta:
- model=User
+ model = User
fields = ['balance']
+
+
+class WalletBalanceSerializer(serializers.ModelSerializer):
+ instance_id = serializers.UUIDField(required=True)
+ instance_type = serializers.ChoiceField(choices=TypeChoices.choices, required=True)
+
+ class Meta:
+ model = User
+ fields = ['balance', 'instance_id', 'instance_type']
+
+
+class TransferSubmitSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Transaction
+ fields = ('uuid',
+ 'payer_id',
+ 'payer_type',
+ 'payee_id',
+ 'payee_type',
+ "created_at",
+ "state",
+ 'amount',
+ # 'delay',
+ 'updated_at',
+ 'details',
+ )
+
+ read_only_fields = ['uuid', 'updated_at', 'state', 'created_at']
+
+
+class TransferVerifySerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Transaction
+ fields = ('uuid',
+ 'payer_id',
+ 'payer_type',
+ 'payee_id',
+ 'payee_type',
+ "created_at",
+ "state",
+ 'amount',
+ # 'delay',
+ 'updated_at',
+ 'details',
+ )
+
+ read_only_fields = ['uuid', 'payer_id', 'payer_type', 'payee_id', 'payee_type', 'updated_at', 'state',
+ 'amount', 'created_at']
diff --git a/apps/wallet/urls.py b/apps/wallet/urls.py
index f780ed9..0c73da6 100644
--- a/apps/wallet/urls.py
+++ b/apps/wallet/urls.py
@@ -1,28 +1,30 @@
from django.urls import path
-from .application_views import ApplicationWalletBalanceView
-from .views import TransactionList, TransactionDetail, TransactionPay, TransactionReceipt, \
- WithdrawVerify, WithdrawSubmit, DepositSubmit, DepositVerify, TransactionListView, TransactionDetailView, \
- WalletBalanceView
+from .views import application as application_views
+from .views import user as user_views
app_name = "wallet"
urlpatterns = [
- path('transactions/', TransactionListView.as_view(), name='transaction_list'),
- path('transactions/', TransactionDetailView.as_view(), name='transaction_detail'),
-
- path('api/wallet/balance', WalletBalanceView.as_view(), name='wallet_balance_api'),
-
- path('api/transactions/', TransactionList.as_view(), name='transaction_list_api'),
- path('api/transactions/', TransactionDetail.as_view(), name='transaction_detail_api'),
- path('api/transactions//pay', TransactionPay.as_view(), name='transaction_pay_api'),
- path('api/transactions//receipt', TransactionReceipt.as_view(), name='transaction_receipt_api'),
+ path('transactions/', user_views.TransactionListView.as_view(), name='user_transaction_list'),
+ path('transactions/', user_views.TransactionDetailView.as_view(), name='user_transaction_detail'),
#
- path('api/deposit/', DepositSubmit.as_view(), name='deposit_submit_api'),
- path('api/deposit//verify', DepositVerify.as_view(), name='deposit_verify_api'),
+ # path('api/wallet/balance', MyWalletBalanceView.as_view(), name='wallet_balance_api'),
#
- path('api/withdraw/', WithdrawSubmit.as_view(), name='withdraw_submit_api'),
- path('api/withdraw//verify', WithdrawVerify.as_view(), name='withdraw_verify_api'),
+ # path('api/transactions/', TransactionList.as_view(), name='transaction_list_api'),
+ # path('api/transactions/', TransactionDetail.as_view(), name='transaction_detail_api'),
+ # path('api/transactions//pay', TransactionPay.as_view(), name='transaction_pay_api'),
+ # path('api/transactions//receipt', TransactionReceipt.as_view(), name='transaction_receipt_api'),
+ # #
+ # path('api/deposit/', DepositSubmit.as_view(), name='deposit_submit_api'),
+ # path('api/deposit//verify', DepositVerify.as_view(), name='deposit_verify_api'),
+ # #
+ # path('api/withdraw/', WithdrawSubmit.as_view(), name='withdraw_submit_api'),
+ # path('api/withdraw//verify', WithdrawVerify.as_view(), name='withdraw_verify_api'),
+
+ path('api/get_balance/', application_views.WalletBalanceView.as_view(), name='wallet_balance'),
+ path('api/transaction/transfer_submit/', application_views.TransferSubmitView.as_view(), name='transfer_submit'),
+ path('api/transaction//transfer_verify/', application_views.TransferVerifyView.as_view(), name='transfer_verify'),
+ path('api/transaction//', application_views.TransactionDetailView.as_view(), name='transaction_detail'),
- path('api/get_balance/', ApplicationWalletBalanceView.as_view(), name='wallet_balance'),
]
\ No newline at end of file
diff --git a/apps/wallet/views/__init__.py b/apps/wallet/views/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/wallet/views/application.py b/apps/wallet/views/application.py
new file mode 100755
index 0000000..c8779ba
--- /dev/null
+++ b/apps/wallet/views/application.py
@@ -0,0 +1,83 @@
+from django.db.models import Q
+from django.http import Http404
+from django.shortcuts import get_object_or_404
+from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
+from rest_framework import generics, permissions, mixins
+from rest_framework.response import Response
+
+from apps.gooyal_oauth2.models import Application
+from apps.users.models import User
+from apps.wallet.constans import TypeChoices
+from apps.wallet.models import Transaction
+from apps.wallet.serializers import TransactionSerializer, WalletBalanceSerializer, TransferSubmitSerializer, \
+ TransferVerifySerializer
+
+
+def get_application(request):
+ try:
+ application = request.auth.application
+ except:
+ application = None
+ return application
+
+
+class WalletBalanceView(generics.RetrieveAPIView):
+ '''
+ API for getting specified user or application balance.
+ '''
+ permission_classes = [TokenHasScope]
+ serializer_class = WalletBalanceSerializer
+ required_scopes = ['wallet.application:get_balance']
+
+ def get_object(self):
+ serializer = self.get_serializer(data=self.request.data)
+ serializer.is_valid(raise_exception=True)
+ instance_id = serializer.data.get('instance_id')
+ instance_type = serializer.data.get('instance_type')
+ if instance_type == TypeChoices.USER:
+ queryset = User.objects.filter(pk=instance_id)
+ elif instance_type == TypeChoices.APPLICATION:
+ queryset = Application.objects.filter(pk=instance_id)
+ else:
+ raise Http404(
+ "No object matches the given query."
+ )
+
+ instance = get_object_or_404(queryset)
+ return instance
+
+ def retrieve(self, request, *args, **kwargs):
+ return super().retrieve(request, *args, **kwargs)
+
+
+class TransactionDetailView(generics.RetrieveAPIView):
+ permission_classes = [TokenHasScope]
+ serializer_class = TransactionSerializer
+ lookup_field = 'uuid'
+ required_scopes = ['wallet.application:transaction_detail']
+
+
+class TransferSubmitView(generics.CreateAPIView):
+ permission_classes = [TokenHasScope]
+ serializer_class = TransferSubmitSerializer
+ lookup_field = 'uuid'
+ required_scopes = ['wallet.application:transfer_submit']
+
+ def perform_create(self, serializer):
+ instance:Transaction = serializer.save()
+ application = get_application(self.request)
+ instance.application = application
+ instance.submit()
+
+
+class TransferVerifyView(generics.UpdateAPIView):
+ permission_classes = [TokenHasScope]
+ serializer_class = TransferVerifySerializer
+ lookup_field = 'uuid'
+ required_scopes = ['wallet.application:transfer_verify']
+ queryset = Transaction.objects.all()
+
+ def perform_update(self, serializer):
+ instance:Transaction = serializer.save()
+ instance.verify()
+
diff --git a/apps/wallet/views.py b/apps/wallet/views/user.py
similarity index 95%
rename from apps/wallet/views.py
rename to apps/wallet/views/user.py
index caabc83..1f406e9 100755
--- a/apps/wallet/views.py
+++ b/apps/wallet/views/user.py
@@ -12,9 +12,10 @@ from rest_framework.response import Response
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
from apps.wallet.models import Transaction
-from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, WalletSerializer
-from .constans import StateChoices
-from .forms import TransactionActionForm
+from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
+ MyWalletSerializer
+from ..constans import StateChoices
+from ..forms import TransactionActionForm
from django.utils.translation import gettext_lazy as _
@@ -27,12 +28,12 @@ def get_application(request):
return application
-class WalletBalanceView(generics.RetrieveAPIView):
+class MyWalletBalanceView(generics.RetrieveAPIView):
'''
API for getting authenticated user balance.
'''
permission_classes = [AllowAny, permissions.IsAuthenticated, IsAuthenticatedOrTokenHasScope]
- serializer_class = WalletSerializer
+ serializer_class = MyWalletSerializer
required_scopes = ['wallet.application:get_balance']
def get_object(self):
@@ -183,7 +184,7 @@ class TransactionListView(ListView):
def get_queryset(self):
user = self.request.user
- return Transaction.objects.filter(Q(payee=user) | Q(payer=user)).all()
+ return Transaction.objects.filter(Q(payee_id=user.pk) | Q(payer_id=user.pk)).all()
@method_decorator(login_required, name='dispatch')
@@ -213,4 +214,3 @@ class TransactionDetailView(UpdateView):
result = transaction.receipt()
messages.error(self.request, _('success.'))
return super().form_valid(form)
-
diff --git a/templates/include/inline_transaction_data.html b/templates/include/inline_transaction_data.html
index 62da770..2f3810b 100644
--- a/templates/include/inline_transaction_data.html
+++ b/templates/include/inline_transaction_data.html
@@ -7,7 +7,7 @@
amount:
- {{ transaction.amount }}
+ {{ transaction.amount }}
diff --git a/templates/include/top_bar.html b/templates/include/top_bar.html
index d816170..dbe3244 100644
--- a/templates/include/top_bar.html
+++ b/templates/include/top_bar.html
@@ -16,7 +16,7 @@
diff --git a/wallet/settings.py b/wallet/settings.py
index 6a938c1..69828b9 100644
--- a/wallet/settings.py
+++ b/wallet/settings.py
@@ -144,7 +144,7 @@ TEMPLATES = [
AUTHENTICATION_BACKENDS = (
'oauth2_provider.backends.OAuth2Backend',
- # 'django.contrib.auth.backends.ModelBackend',
+ 'django.contrib.auth.backends.ModelBackend',
)
WSGI_APPLICATION = 'wallet.wsgi.application'
diff --git a/wallet/urls.py b/wallet/urls.py
index d31a443..9d9d6d9 100644
--- a/wallet/urls.py
+++ b/wallet/urls.py
@@ -23,8 +23,6 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, Spec
# from apps.gooyal_oauth2.views.introspect import introspect_token
-from apps.wallet.views import TransactionList, TransactionDetail, TransactionPay, TransactionReceipt, \
- WithdrawVerify, WithdrawSubmit, DepositSubmit, DepositVerify
from django.contrib.auth import urls as auth_urls