from django.db import transaction from order.models import Cart from django.utils.translation import override from .permissons import PaymentCallBackPermissions from azbankgateways.models.enum import PaymentStatus from azbankgateways.models import Bank from rest_framework import serializers from rest_framework.response import Response from django.utils import timezone from django.shortcuts import render from rest_framework.views import APIView, Response from django.shortcuts import get_object_or_404 from product.models import ProductVariant 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 .permissons import CanDeleteCartItemPermissions, GetOrderPermission, SetAddressPermissions from azbankgateways import bankfactories, models as bank_models from azbankgateways.exceptions import AZBankGatewaysException from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes, extend_schema_view from utils.pagination import StructurePagination from order.models import OrderModel from django.urls import reverse from account.models import UserAddressModel # try: # pass # except DiscountNotAvailableError: # pass @extend_schema_view( post=extend_schema(tags=["cart discount code"]), delete=extend_schema(tags=["cart discount code"]), ) class ApplyDiscountView(APIView): serializer_class = DiscountCodeSerializer permission_classes = [IsAuthenticated] def post(self, request): cart_order, created = Cart.objects.get_or_create( user=request.user, ) discount_code = get_object_or_404( DiscountCode, code=request.data.get('code')) if not discount_code.is_valid(): return Response({'detail': discount_code.not_valid_reason()}, status=status.HTTP_400_BAD_REQUEST) cart_order.discount_code = discount_code cart_order.save() return Response({'detail': 'کد تخفیف با موفقیت اعمال شد'}, status=status.HTTP_200_OK) def delete(self, request): cart_order, created = Cart.objects.get_or_create( user=request.user, ) cart_order.discount_code = None cart_order.save() return Response({'detail': 'کد تخفیف با موفقیت حذف شد'}, status=status.HTTP_204_NO_CONTENT) @extend_schema_view( post=extend_schema(tags=["cart special discount code"]), delete=extend_schema(tags=["cart special discount code"]), ) class ApplySpecialDiscountView(APIView): permission_classes = [IsAuthenticated] def post(self, request): from account.models import SpecialDiscountCode cart, created = Cart.objects.get_or_create(user=request.user) code = request.data.get('code') if not code: return Response({'detail': 'کد تخفیف ویژه را وارد کنید'}, status=status.HTTP_400_BAD_REQUEST) try: special_discount_code = SpecialDiscountCode.objects.get(code=code) except SpecialDiscountCode.DoesNotExist: return Response({'detail': 'کد تخفیف ویژه معتبر نیست'}, status=status.HTTP_404_NOT_FOUND) # Apply the special discount code to cart cart.special_discount_code = special_discount_code cart.save() return Response({'detail': 'کد تخفیف ویژه با موفقیت اعمال شد'}, status=status.HTTP_200_OK) def delete(self, request): cart, created = Cart.objects.get_or_create(user=request.user) cart.special_discount_code = None cart.save() return Response({'detail': 'کد تخفیف ویژه با موفقیت حذف شد'}, status=status.HTTP_204_NO_CONTENT) class CartItemClear(APIView): permission_classes = [IsAuthenticated] serializer_class = OrderItemSerailzier @extend_schema( tags=["order cart"] ) def delete(self, request): cart_order, created = Cart.objects.get_or_create( user=request.user, ) cart_order.items.all().delete() return Response({'detail': f'سبد خرید با موفقیت خالی شد'}, status=status.HTTP_204_NO_CONTENT) @extend_schema_view( post=extend_schema(tags=["order cart"]), delete=extend_schema(tags=["order cart"]), ) class CartItemViews(APIView): permission_classes = [IsAuthenticated] serializer_class = OrderItemSerailzier def post(self, request, pk): product_variant = get_object_or_404(ProductVariant, pk=pk) response = 'محصول با موفقیت به سبد خرید اضافه شد' quantity = request.data.get('quantity', 1) quantity = max(quantity, 0) if product_variant.in_stock < quantity: quantity = product_variant.in_stock response = 'تعداد درخواستی بیشتر از موجودی محصول میباشد' cart_order, created = Cart.objects.get_or_create(user=request.user) order_item, created = CartItem.objects.get_or_create( cart=cart_order, product_variant=product_variant, defaults={'quantity': quantity}) if not created and order_item.quantity: order_item.quantity = quantity order_item.save() if not order_item.quantity: order_item.delete() return Response({'detail': response, 'count': quantity}, status=status.HTTP_202_ACCEPTED) def delete(self, request, pk): cart_item = get_object_or_404(CartItem, pk=pk) permission = CanDeleteCartItemPermissions() if not permission.has_object_permission(request, self, cart_item): return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN) cart_item.delete() return Response( {"detail": "محصول با موفقیت از سبد خرید شما حذف شد"}, status=status.HTTP_204_NO_CONTENT, ) class CartView(APIView): permission_classes = [IsAuthenticated] serializer_class = CartSerializer @extend_schema( tags=["order cart"] ) def get(self, request): user = request.user cart_instance, created = Cart.objects.get_or_create(user=user) cart_ser = self.serializer_class( instance=cart_instance, context={'request': request}) return Response(cart_ser.data, status=status.HTTP_200_OK) class OrderlistView(APIView): permission_classes = [IsAuthenticated] serializer_class = OrderListSerializer 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=( "['ADMIN_PENDING', 'PENDING', 'POSTED', 'RECEIVED', 'CANCELED', 'REFUNDED']" ), 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, ), ], tags=["order"] ) def get(self, request): user = request.user orders = OrderModel.objects.filter(user=user).exclude(status="CART") status_filter = request.query_params.get("status", None) sort = request.query_params.get('sort', None) if status_filter in ['ADMIN_PENDING', 'PENDING', 'POSTED', 'RECEIVED', 'CANCELED', 'REFUNDED']: 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) class OrderGetView(APIView): permission_classes = [IsAuthenticated, GetOrderPermission] serializer_class = OrderGetSerializer def get(self, request, pk): order_object = get_object_or_404(OrderModel, pk=pk) permission = GetOrderPermission() if not permission.has_object_permission(request, self, order_object): return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN) order_ser = self.serializer_class( order_object, context={'request': request}) return Response(order_ser.data, status=status.HTTP_200_OK) class BankTypeSerializer(serializers.Serializer): gateway_type = serializers.ChoiceField( choices=['ZIBAL', 'BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'BAHAMTA', 'MELLAT', 'PAYV1']) class PaymentView(APIView): permission_classes = [IsAuthenticated] serializer_class = BankTypeSerializer @extend_schema( description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']", tags=['order payment'] ) def post(self, request): # Get user's cart cart = get_object_or_404(Cart, user=request.user) # Check if cart has items if not cart.items.exists(): return Response( {'error': 'سبد خرید خالی است'}, status=status.HTTP_400_BAD_REQUEST ) # Check if cart has address if not cart.address: return Response( {'error': 'آدرس انتخاب نشده است'}, status=status.HTTP_400_BAD_REQUEST ) # Validate product variant quantities insufficient_stock_items = [] adjusted_items = [] for cart_item in cart.items.all(): if cart_item.product_variant.in_stock < cart_item.quantity: available_stock = cart_item.product_variant.in_stock # Store info about insufficient stock insufficient_stock_items.append({ 'product': cart_item.product_variant.product.name, 'variant': str(cart_item.product_variant), 'requested': cart_item.quantity, 'available': available_stock }) # Auto-adjust the cart item quantity if available_stock > 0: # Reduce quantity to available stock cart_item.quantity = available_stock cart_item.save() adjusted_items.append({ 'product': cart_item.product_variant.product.name, 'variant': str(cart_item.product_variant), 'new_quantity': available_stock }) else: # Remove item if no stock available product_name = cart_item.product_variant.product.name variant_name = str(cart_item.product_variant) cart_item.delete() adjusted_items.append({ 'product': product_name, 'variant': variant_name, 'new_quantity': 0, 'removed': True }) if insufficient_stock_items: # Create error message with product names product_names = [item['product'] for item in insufficient_stock_items] product_list = '، '.join(product_names) return Response({ 'detail': f'موجودی محصولات زیر کافی نیست: {product_list}', 'message': 'تعداد محصولات به صورت خودکار تنظیم شد', 'insufficient_items': insufficient_stock_items, 'adjusted_items': adjusted_items }, status=status.HTTP_400_BAD_REQUEST) try: with transaction.atomic(): # Compute special discounts for cart items if special_discount_code is applied special_total = 0 for cart_item in cart.items.select_related('product_variant').all(): if cart.special_discount_code: special_total += cart_item.special_discount_amount # Create order order = OrderModel.objects.create( user=request.user, address=cart.address, created_at=timezone.now().date(), discount_code=cart.discount_code, special_discount_code=cart.special_discount_code, discount_amount=cart.discount_code_amount, special_discount_total=special_total, tax=cart.tax_amount, final_price=cart.final_price, cart_total=cart.cart_total, status='ADMIN_PENDING', cart=cart ) # Create order items and reduce product variant quantities for cart_item in cart.items.all(): OrderItemModel.objects.create( order=order, quantity=cart_item.quantity, price=cart_item.product_variant.price, product=cart_item.product_variant, discount_percent=cart_item.discount, special_discount_amount=cart_item.special_discount_amount ) # Reduce product variant quantity cart_item.product_variant.in_stock -= cart_item.quantity cart_item.product_variant.save() # Reduce discount code quantity if used if cart.discount_code: cart.discount_code.quantity -= 1 cart.discount_code.save() # Setup payment gateway user_mobile_number = request.user.phone factory = bankfactories.BankFactory() bank = factory.create(bank_models.BankType.ZIBAL) bank.set_request(request) # Use final_price instead of hardcoded amount bank.set_amount(cart.final_price) bank.set_client_callback_url( 'https://heymlz.com/transaction') bank.set_mobile_number(user_mobile_number) bank_record = bank.ready() # Link bank record to order (assuming you have this relationship) bank_record.order = order bank_record.save() return Response({ 'url': bank.get_gateway()['url'], }) except AZBankGatewaysException as e: print(f"Payment gateway error: {e}") return Response({ 'error': 'خطا در اتصال به درگاه پرداخت' }, status=status.HTTP_400_BAD_REQUEST) except Exception as e: print(f"Order creation error: {e}") return Response({ 'error': 'خطا در ثبت سفارش' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class BankCallbackSerializer(serializers.ModelSerializer): status_detail = serializers.SerializerMethodField() bank_type = serializers.SerializerMethodField() amount = serializers.SerializerMethodField() status = serializers.SerializerMethodField() class Meta: model = Bank fields = ['status', 'bank_type', 'tracking_code', 'amount', 'created_at', 'response_result', 'reference_number', 'status_detail'] def get_status_detail(self, obj): with override('fa'): return obj.get_status_display() def get_bank_type(self, obj): with override('fa'): return obj.get_bank_type_display() def get_amount(self, obj): return f'{int(obj.amount):,.0f} تومان' def get_status(self, obj): if obj.status in { PaymentStatus.WAITING, PaymentStatus.REDIRECT_TO_BANK, PaymentStatus.RETURN_FROM_BANK, }: return "pending" elif obj.status in { PaymentStatus.CANCEL_BY_USER, PaymentStatus.EXPIRE_GATEWAY_TOKEN, PaymentStatus.EXPIRE_VERIFY_PAYMENT, PaymentStatus.ERROR, }: return "canceled" elif obj.status == PaymentStatus.COMPLETE: return "succeeded" return "unknown" class CallbackView(APIView): serializer_class = BankCallbackSerializer permission_classes = [IsAuthenticated] def get(self, request, tracking_code): if not tracking_code: return Response({'detail': 'تریسکد خالی است.'}, status=status.HTTP_400_BAD_REQUEST) try: bank_record = bank_models.Bank.objects.get( tracking_code=tracking_code) permission = PaymentCallBackPermissions() if not permission.has_object_permission(request, self, bank_record): return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN) bank_record_ser = self.serializer_class( instance=bank_record, context={'request': request}) except bank_models.Bank.DoesNotExist: return Response({'detail': 'کد تریسکد معتبر نمیباشد.'}, status=status.HTTP_404_NOT_FOUND) if bank_record.is_success: order = bank_record.order order.cart.clear_cart() order.is_paid = True order.save() return Response({"detail": "پرداخت با موفقیت انجام شد.", "bank_result": bank_record_ser.data}, status=status.HTTP_200_OK) else: order = bank_record.order order.rollback_stock() return Response( { "detail": "پرداخت ناموفق بود. در صورت کسر وجه، مبلغ حداکثر تا ۴۸ ساعت آینده به حساب شما بازگردانده می‌شود.", "bank_result": bank_record_ser.data, }, status=status.HTTP_200_OK, ) class SetAddressSerilizer(serializers.Serializer): address_id = serializers.IntegerField() class SetAddressForCartView(APIView): serializer_class = SetAddressSerilizer permission_classes = [IsAuthenticated, SetAddressPermissions] @extend_schema( tags=["order cart"] ) def post(self, request): address_id = request.data.get('address_id', None) if not address_id: return Response({'detail': 'address_id را ارسال کنید'}, status=status.HTTP_400_BAD_REQUEST) address_object = get_object_or_404(UserAddressModel, pk=address_id) permission = SetAddressPermissions() if not permission.has_object_permission(request, self, address_object): return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN) cart_order, created = Cart.objects.get_or_create( user=request.user, ) cart_order.address = address_object cart_order.save() return Response({'detail': 'ادرس با موفقیت انتخاب شد'}) from django.http import HttpResponse, HttpResponseForbidden, HttpResponseNotFound from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required from .invoice_generator import generate_order_invoice, generate_shop_order_invoice class UserOrderInvoiceView(APIView): """ API endpoint for authenticated users to download their own order invoice PDF. Users can only download invoices for orders that belong to them. """ permission_classes = [IsAuthenticated] @extend_schema( tags=["order invoice"], description="Download PDF invoice for the authenticated user's order.", responses={200: OpenApiTypes.BINARY}, ) def get(self, request, order_id): from .models import OrderModel try: order = OrderModel.objects.get(pk=order_id) except OrderModel.DoesNotExist: return Response( {'detail': 'سفارش مورد نظر یافت نشد'}, status=status.HTTP_404_NOT_FOUND, ) if order.user != request.user: return Response( {'detail': 'شما اجازه دسترسی به این فاکتور را ندارید'}, status=status.HTTP_403_FORBIDDEN, ) if not order.is_paid: return Response( {'detail': 'فاکتور فقط برای سفارش‌های پرداخت شده قابل دانلود است'}, status=status.HTTP_400_BAD_REQUEST, ) try: pdf_file = generate_order_invoice(order_id) response = HttpResponse(pdf_file.read(), content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="invoice_{order_id}.pdf"' return response except Exception as e: return Response( {'detail': f'خطا در ایجاد فاکتور: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @login_required def download_order_invoice(request, order_id): """ Download invoice PDF for a specific OrderModel. Requires superuser permissions. """ if not request.user.is_superuser: return HttpResponseForbidden("شما اجازه دسترسی به این فاکتور را ندارید") try: pdf_file = generate_order_invoice(order_id) response = HttpResponse(pdf_file.read(), content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="order_invoice_{order_id}.pdf"' return response except ValueError as e: return HttpResponseNotFound(f"خطا: {str(e)}") except Exception as e: return HttpResponse(f"خطا در ایجاد فاکتور: {str(e)}", status=500) @login_required def download_shop_order_invoice(request, shop_order_id): """ Download invoice PDF for a specific ShopOrderModel. Shop owners can only download their own invoices, admins can download all. """ from .models import ShopOrderModel try: shop_order = ShopOrderModel.objects.get(pk=shop_order_id) # Check permissions if not request.user.is_staff and not request.user.is_superuser: if not hasattr(request.user, 'shop') or request.user.shop != shop_order.shop: return HttpResponseForbidden("شما اجازه دسترسی به این فاکتور را ندارید") pdf_file = generate_shop_order_invoice(shop_order_id) response = HttpResponse(pdf_file.read(), content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="shop_order_invoice_{shop_order_id}.pdf"' return response except ShopOrderModel.DoesNotExist: return HttpResponseNotFound("فاکتور مورد نظر یافت نشد") except Exception as e: return HttpResponse(f"خطا در ایجاد فاکتور: {str(e)}", status=500)