Files
hossein-por-shop/backend/order/views.py
T
2025-09-23 10:17:19 +03:30

435 lines
17 KiB
Python

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)
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):
order_item = get_object_or_404(OrderItemModel, pk=pk)
permission = CanDeleteCartItemPermissions()
if not permission.has_object_permission(request, self, order_item):
return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN)
order_item.delete()
return Response(
{"detail": "محصول با موفقیت از سبد خرید شما حذف شد"},
status=status.HTTP_204_NO_CONTENT,
)
from order.models import Cart
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)
from rest_framework import serializers
class BankTypeSerializer(serializers.Serializer):
gateway_type = serializers.ChoiceField(choices=['ZIBAL', 'BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'BAHAMTA', 'MELLAT', 'PAYV1'])
from django.db import transaction
from django.utils import timezone
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):
print(request.data.get('gateway_type'))
# 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 = []
for cart_item in cart.items.all():
if cart_item.product_variant.in_stock < cart_item.quantity:
insufficient_stock_items.append({
'product': cart_item.product_variant.product.name,
'variant': str(cart_item.product_variant),
'requested': cart_item.quantity,
'available': cart_item.product_variant.in_stock
})
if insufficient_stock_items:
return Response({
'error': 'موجودی برخی محصولات کافی نیست',
'insufficient_items': insufficient_stock_items
}, status=status.HTTP_400_BAD_REQUEST)
# Validate discount code if present
if cart.discount_code and not cart.discount_code.is_valid():
return Response({
'error': cart.discount_code.not_valid_reason()
}, status=status.HTTP_400_BAD_REQUEST)
# Calculate order totals
cart_total = cart.cart_total
discount_amount = 0
if cart.discount_code:
discount_amount = int((cart_total * cart.discount_code.percent) / 100)
# Calculate tax (assuming 9% tax rate, adjust as needed)
tax_rate = 9 # percent
subtotal = cart_total - discount_amount
tax = int((subtotal * tax_rate) / 100)
final_price = subtotal + tax
try:
with transaction.atomic():
# Create order
order = OrderModel.objects.create(
user=request.user,
address=cart.address,
created_at=timezone.now().date(),
discount_code=cart.discount_code,
discount_amount=discount_amount,
tax=tax,
final_price=final_price,
cart_total=cart_total,
status='ADMIN_PENDING'
)
# 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
)
# 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)
bank.set_amount(final_price) # Use final_price instead of hardcoded amount
bank.set_client_callback_url('http://localhost:3000/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()
# Clear cart after successful order creation
# cart.items.all().delete()
# cart.discount_code = None
# cart.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)
from rest_framework.response import Response
from azbankgateways import bankfactories, models as bank_models
from rest_framework import serializers
from azbankgateways.models import Bank
from azbankgateways.models.enum import PaymentStatus
from .permissons import PaymentCallBackPermissions
from django.utils.translation import override
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:
return Response({"detail" : "پرداخت با موفقیت انجام شد.", "bank_result": bank_record_ser.data}, status=status.HTTP_200_OK)
return Response(
{
"detail": "پرداخت ناموفق بود. در صورت کسر وجه، مبلغ حداکثر تا ۴۸ ساعت آینده به حساب شما بازگردانده می‌شود.",
"bank_result": bank_record_ser.data,
},
status=status.HTTP_200_OK,
)
class SetAddressSerilizer(serializers.Serializer):
address_id = serializers.IntegerField()
from order.models import Cart
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': 'ادرس با موفقیت انتخاب شد'})