diff --git a/backend/core/settings/base.py b/backend/core/settings/base.py index 5c7c324..9572ade 100644 --- a/backend/core/settings/base.py +++ b/backend/core/settings/base.py @@ -240,15 +240,20 @@ AWS_S3_OBJECT_PARAMETERS = { # ============================================================================== AZ_IRANIAN_BANK_GATEWAYS = { - 'GATEWAYS': { - 'ZARINPAL': { - 'MERCHANT_CODE': 'Merchant-Code', - 'SANDBOX': True, - } + "GATEWAYS": { + "ZARINPAL": { + "MERCHANT_CODE": "", + "SANDBOX": 0, + }, }, - 'IS_SAMPLE_FORM_ENABLE': True, - 'DEFAULT_BANK': 'ZARINPAL', - 'CURRENCY': 'IRR', - 'TRACKING_CODE_QUERY_PARAM': 'tc', - 'BANK_PRIORITIES': ['ZARINPAL'], + "IS_SAMPLE_FORM_ENABLE": True, + "DEFAULT": "ZARINPAL", + "CURRENCY": "IRT", + "TRACKING_CODE_QUERY_PARAM": "tc", + "TRACKING_CODE_LENGTH": 16, + "SETTING_VALUE_READER_CLASS": "azbankgateways.readers.DefaultReader", + "BANK_PRIORITIES": [ + "ZARINPAL", + ], + "IS_SAFE_GET_GATEWAY_PAYMENT": False # better to be True } \ No newline at end of file diff --git a/backend/core/urls.py b/backend/core/urls.py index 9db83f5..3e46822 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -8,6 +8,9 @@ from product import views from account.views import CustomTokenObtainPairView from home.views import HomeView from .views import FakeAdminLoginView +from azbankgateways.urls import az_bank_gateways_urls + +admin.autodiscover() urlpatterns = [ @@ -29,6 +32,7 @@ urlpatterns = [ path('blogs/', include('blog.urls')), path('order/', include('order.urls')), path('home/', include('home.urls')), + path("bankgateways/", az_bank_gateways_urls()), path('', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ] diff --git a/backend/order/migrations/0015_payment.py b/backend/order/migrations/0015_payment.py new file mode 100644 index 0000000..fd65876 --- /dev/null +++ b/backend/order/migrations/0015_payment.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.2 on 2025-03-13 16:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0014_alter_orderitemmodel_price'), + ] + + operations = [ + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.PositiveIntegerField()), + ('status', models.CharField(default='Pending', max_length=50)), + ('tracking_code', models.CharField(blank=True, max_length=100)), + ('bank_type', models.CharField(max_length=100)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/backend/order/migrations/0016_rename_payment_paymentmodel.py b/backend/order/migrations/0016_rename_payment_paymentmodel.py new file mode 100644 index 0000000..37cbc3b --- /dev/null +++ b/backend/order/migrations/0016_rename_payment_paymentmodel.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.2 on 2025-03-13 16:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0015_payment'), + ] + + operations = [ + migrations.RenameModel( + old_name='Payment', + new_name='PaymentModel', + ), + ] diff --git a/backend/order/models.py b/backend/order/models.py index a37a45f..69be701 100644 --- a/backend/order/models.py +++ b/backend/order/models.py @@ -120,3 +120,13 @@ class OrderItemModel(models.Model): def __str__(self): return f'({self.product}) - ({self.order.user})' + +#TODO complate this shit +class PaymentModel(models.Model): + amount = models.PositiveIntegerField() + status = models.CharField(max_length=50, default='Pending') + tracking_code = models.CharField(max_length=100, blank=True) + bank_type = models.CharField(max_length=100) + created_at = models.DateTimeField(auto_now_add=True) + def __str__(self): + return 'payment' \ No newline at end of file diff --git a/backend/order/serializers.py b/backend/order/serializers.py index e0b3b2c..a9b3601 100644 --- a/backend/order/serializers.py +++ b/backend/order/serializers.py @@ -14,7 +14,7 @@ class ProductVariantSerialzier(serializers.ModelSerializer): category = serializers.SerializerMethodField() class Meta: model = ProductVariant - fields = ['title', 'product_attributes', 'in_stock', 'price', 'discount', 'color', 'image', 'discount_amount', 'category', 'final_price'] + fields = ['id', 'title', 'product_attributes', 'in_stock', 'price', 'discount', 'color', 'image', 'discount_amount', 'category', 'final_price'] def get_discount_amount(self, obj): discount_amount = int(obj.price * (obj.discount / 100)) diff --git a/backend/order/urls.py b/backend/order/urls.py index d29e3b5..c439005 100644 --- a/backend/order/urls.py +++ b/backend/order/urls.py @@ -2,6 +2,7 @@ from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include from .views import CartItemViews, CartView, OrderlistView, CartItemClear, ApplyDiscountView +from .views import PaymentView, callback_view urlpatterns = [ path('all', OrderlistView.as_view(), name='order-list'), @@ -9,6 +10,6 @@ urlpatterns = [ path('cart/discount', ApplyDiscountView.as_view()), path('cart/all', CartItemClear.as_view()), path('cart/item/', CartItemViews.as_view(), name='change-item-cart'), - # path('payment', CartView.as_view()), - # path('', CartView.as_view()), + path('payment', PaymentView.as_view(), name='payment'), + path('callback', callback_view, name='callback-gateway'), ] diff --git a/backend/order/views.py b/backend/order/views.py index ebfec28..468138b 100644 --- a/backend/order/views.py +++ b/backend/order/views.py @@ -7,13 +7,17 @@ from rest_framework.permissions import IsAuthenticated from .serializers import * # from cart.models import from rest_framework import status -from .models import OrderItemModel, OrderModel, DiscountCode +from .models import OrderItemModel, OrderModel, DiscountCode, PaymentModel from .permissons import CanDeleteCartItemPermissions +from azbankgateways import bankfactories, models as bank_models +from azbankgateways.exceptions import AZBankGatewaysException +from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes +from utils.pagination import StructurePagination try: pass except DiscountNotAvailableError: pass - +from django.urls import reverse """ add post @@ -105,69 +109,110 @@ class CartView(APIView): class OrderlistView(APIView): permission_classes = [IsAuthenticated] serializer_class = OrderSerializer + pagination_class = StructurePagination + @extend_schema( + parameters=[ + OpenApiParameter( + name="limit", + description="لیمیتش", + required=False, + type=OpenApiTypes.INT, + ), + OpenApiParameter( + name="offset", + description="افستش", + required=False, + type=OpenApiTypes.INT, + ), + OpenApiParameter( + name="status", + description=( + "['CART', 'ADMIN_PENDING', 'PENDING', 'POSTED', 'RECEIVED', 'CANCELED', 'BACK']" + ), + required=False, + type=OpenApiTypes.STR, + ), + OpenApiParameter( + name="sort", + description=( + "Sort results by one of the following fields:\n" + "['created_at', '-created_at', 'final_price', '-final_price']" + "\nPrefix with `-` for descending order." + ), + required=False, + type=OpenApiTypes.STR, + ), + ] + ) def get(self, request): user = request.user orders = OrderModel.objects.filter(user=user).exclude(status="CART") - orders_ser = self.serializer_class(instance=orders, many=True, context={'request': request}) - return Response(orders_ser.data, status=status.HTTP_200_OK) + status_filter = request.query_params.get("status", None) + sort = request.query_params.get('sort', None) + if status_filter in ['CART', 'ADMIN_PENDING', 'PENDING', 'POSTED', 'RECEIVED', 'CANCELED', 'BACK']: + orders.filter(status=status_filter) + if sort: + if sort not in ['created_at', '-created_at', 'final_price', '-final_price']: + return Response({'detail': 'پارامتر sort اشتباه است'}, status=status.HTTP_400_BAD_REQUEST) + orders = orders.order_by(sort) + paginator = self.pagination_class() + paginated_orders = paginator.paginate_queryset(orders, request) + orders_ser = self.serializer_class(instance=paginated_orders, many=True, context={'request': request}) + return paginator.get_paginated_response(orders_ser.data) -# from rest_framework.views import APIView -# from rest_framework.response import Response -# from rest_framework import status -# from azbankgateways import bankfactories, models as bank_models -# class PaymentView(APIView): -# def post(self, request): -# amount = request.data.get('amount') -# user = request.user +class PaymentView(APIView): + def post(self, request): + amount = 10000000 + user_mobile_number = request.user.phone + factory = bankfactories.BankFactory() + try: + bank = ( + factory.create(bank_models.BankType.ZARINPAL) + ) + bank.set_request(request) + bank.set_amount(amount) -# payment = Payment.objects.create(amount=amount, bank_type='ZARINPAL') + bank.set_client_callback_url(request.build_absolute_uri(reverse("callback-gateway"))) + bank.set_mobile_number(user_mobile_number) - -# factory = bankfactories.ZarinpalBankFactory() -# try: -# bank = factory.create( -# amount=amount, -# user=user, -# callback_url='http://.com/callback/', -# reference_model=payment, -# ) -# bank.ready() -# return Response({'gateway_url': bank.redirect_url}, status=status.HTTP_200_OK) -# except Exception as e: -# return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + bank_record = bank.ready() + return Response(bank.redirect_gateway().url) + except AZBankGatewaysException as e: + print(e) + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + return Response({'gateway_url': bank.redirect_url}, status=status.HTTP_200_OK) + + -# from django.views.decorators.csrf import csrf_exempt -# from rest_framework.decorators import api_view -# from rest_framework.response import Response -# from azbankgateways import bankfactories, models as bank_models +from django.views.decorators.csrf import csrf_exempt +from rest_framework.decorators import api_view +from rest_framework.response import Response +from azbankgateways import bankfactories, models as bank_models -# @csrf_exempt -# @api_view(['POST']) -# def callback_view(request): -# tracking_code = request.POST.get('tracking_code') -# payment_id = request.POST.get('payment_id') +@csrf_exempt +@api_view(['POST']) +def callback_view(request): + tracking_code = request.GET.get(settings.TRACKING_CODE_QUERY_PARAM, None) + if not tracking_code: + logging.debug("این لینک معتبر نیست.") + raise Http404 -# payment = Payment.objects.get(id=payment_id) -# bank_type = payment.bank_type + try: + bank_record = bank_models.Bank.objects.get(tracking_code=tracking_code) + except bank_models.Bank.DoesNotExist: + logging.debug("این لینک معتبر نیست.") + raise Http404 - -# factory = bankfactories.BankFactory.get_bank(bank_type) -# try: -# result = factory.verify_transaction(tracking_code) -# if result.is_success: -# payment.status = 'Paid' -# payment.tracking_code = tracking_code -# payment.save() -# return Response({'status': 'Payment successful'}) -# else: -# payment.status = 'Failed' -# payment.save() -# return Response({'status': 'Payment failed'}) -# except Exception as e: -# return Response({'error': str(e)}) \ No newline at end of file + if bank_record.is_success: + return HttpResponse("پرداخت با موفقیت انجام شد.") + + + return HttpResponse( + "پرداخت با شکست مواجه شده است. اگر پول کم شده است ظرف مدت ۴۸ ساعت پول به حساب شما بازخواهد گشت." + ) \ No newline at end of file diff --git a/backend/product/serializers.py b/backend/product/serializers.py index 68ac5a6..cd65826 100644 --- a/backend/product/serializers.py +++ b/backend/product/serializers.py @@ -2,7 +2,7 @@ from .models import * from rest_framework import serializers from django.utils import timezone from datetime import timedelta - +from django.contrib.auth.models import AnonymousUser @@ -49,6 +49,7 @@ class ProductVariantSerialzier(serializers.ModelSerializer): in_pack_items = InPackItemsSerialzier(many=True) images = ProductImageSerailizer(many=True) details = ProductDetailSerializer(many=True, read_only=True) + cart_quantity = serializers.SerializerMethodField() class Meta: model = ProductVariant exclude = ('min_price', 'sell', 'currency', 'product', 'input_price') @@ -60,6 +61,11 @@ class ProductVariantSerialzier(serializers.ModelSerializer): if view_type == 'list': self.fields.pop('in_pack_items', None) + def get_cart_quantity(self, obj): + request = self.context.get('request') + if not request or not request.user.is_authenticated: + return 0 + return 1 @@ -135,7 +141,7 @@ class DynamicProductSerializer(serializers.ModelSerializer): many=True, context={ 'view_type': 'list', - 'dollor_price': self.context.get('dollor_price') + 'request': self.context.get('request') } ) return serializer.data diff --git a/backend/product/views.py b/backend/product/views.py index ea29436..79b1c60 100644 --- a/backend/product/views.py +++ b/backend/product/views.py @@ -51,7 +51,7 @@ class AllCategories(APIView): class ProductView(APIView): serializer_class = DynamicProductSerializer permission_classes = [AllowAny] - authentication_classes = [] + # authentication_classes = [] def get(self, request, pk): product = get_object_or_404(ProductModel, id=pk) product_ser = self.serializer_class(instance=product, many=False, context={'request': request, 'view_type': 'instance'}) diff --git a/frontend/public/sw.js b/frontend/public/sw.js index d8942a8..acebd1f 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -5,7 +5,7 @@ precacheAndRoute(self.__WB_MANIFEST); // Version -const VERSION = "1.0.2"; +const VERSION = "1.0.3"; // Service Worker Installation self.addEventListener("install", (event) => {