finish payment logic
This commit is contained in:
+12
-1
@@ -42,10 +42,21 @@ class BankRecordInline(StackedInline):
|
|||||||
return [field.name for field in self.model._meta.fields]
|
return [field.name for field in self.model._meta.fields]
|
||||||
|
|
||||||
|
|
||||||
|
class CartItemInline(StackedInline):
|
||||||
|
model = CartItem
|
||||||
|
extra = 0
|
||||||
|
max_num = 0
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_add_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
@admin.register(Cart)
|
@admin.register(Cart)
|
||||||
class CartAdmin(ModelAdmin):
|
class CartAdmin(ModelAdmin):
|
||||||
pass
|
inlines = [CartItemInline]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-09-23 07:24
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0035_remove_cartitem_discount'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='cart',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='order.cart'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-09-23 07:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0036_ordermodel_cart'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='is_stock_rolled_back',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='موجودی برگردانده شده'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -55,6 +55,11 @@ class Cart(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Cart for {self.user.email}"
|
return f"Cart for {self.user.email}"
|
||||||
|
|
||||||
|
def clear_cart(self):
|
||||||
|
self.items.all().delete()
|
||||||
|
self.discount_code = None
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discount_code_amount(self):
|
def discount_code_amount(self):
|
||||||
if self.discount_code:
|
if self.discount_code:
|
||||||
@@ -151,6 +156,11 @@ class OrderModel(models.Model):
|
|||||||
cart_total = models.BigIntegerField(
|
cart_total = models.BigIntegerField(
|
||||||
null=True, blank=True, verbose_name='کل سبد خرید')
|
null=True, blank=True, verbose_name='کل سبد خرید')
|
||||||
|
|
||||||
|
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
is_stock_rolled_back = models.BooleanField(
|
||||||
|
default=False, verbose_name="موجودی برگردانده شده")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'سفارش: {self.pk + 1000}'
|
return f'سفارش: {self.pk + 1000}'
|
||||||
|
|
||||||
@@ -158,6 +168,34 @@ class OrderModel(models.Model):
|
|||||||
verbose_name = 'سفارش'
|
verbose_name = 'سفارش'
|
||||||
verbose_name_plural = 'سفارشات'
|
verbose_name_plural = 'سفارشات'
|
||||||
|
|
||||||
|
def rollback_stock(self):
|
||||||
|
"""
|
||||||
|
Rollback stock quantities for all items in this order
|
||||||
|
Returns True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
if self.is_stock_rolled_back:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if not self.cart:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get all cart items and rollback their stock
|
||||||
|
for order_item in self.items.all():
|
||||||
|
product = order_item.product
|
||||||
|
# Add back the quantity to stock
|
||||||
|
product.stock_quantity += order_item.quantity
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
# Mark as rolled back
|
||||||
|
self.is_stock_rolled_back = True
|
||||||
|
self.save(update_fields=['is_stock_rolled_back'])
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Log the error if you have logging setup
|
||||||
|
# logger.error(f"Failed to rollback stock for order {self.pk}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
class OrderItemModel(models.Model):
|
class OrderItemModel(models.Model):
|
||||||
order = models.ForeignKey(
|
order = models.ForeignKey(
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ def udpate_bank_status():
|
|||||||
bank.verify(item.tracking_code)
|
bank.verify(item.tracking_code)
|
||||||
bank_record = bank_models.Bank.objects.get(tracking_code=item.tracking_code)
|
bank_record = bank_models.Bank.objects.get(tracking_code=item.tracking_code)
|
||||||
if bank_record.is_success:
|
if bank_record.is_success:
|
||||||
|
bank_record.order.cart.clear_cart()
|
||||||
logging.debug("This record is verify now.", extra={"pk": bank_record.pk})
|
logging.debug("This record is verify now.", extra={"pk": bank_record.pk})
|
||||||
|
else:
|
||||||
|
order = bank_record.order
|
||||||
|
order.rollback_stock()
|
||||||
return 'update bank record is done'
|
return 'update bank record is done'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+49
-32
@@ -205,7 +205,7 @@ class PaymentView(APIView):
|
|||||||
tags=['order payment']
|
tags=['order payment']
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
print(request.data.get('gateway_type'))
|
|
||||||
|
|
||||||
# Get user's cart
|
# Get user's cart
|
||||||
cart = get_object_or_404(Cart, user=request.user)
|
cart = get_object_or_404(Cart, user=request.user)
|
||||||
@@ -226,39 +226,55 @@ class PaymentView(APIView):
|
|||||||
|
|
||||||
# Validate product variant quantities
|
# Validate product variant quantities
|
||||||
insufficient_stock_items = []
|
insufficient_stock_items = []
|
||||||
|
adjusted_items = []
|
||||||
|
|
||||||
for cart_item in cart.items.all():
|
for cart_item in cart.items.all():
|
||||||
if cart_item.product_variant.in_stock < cart_item.quantity:
|
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({
|
insufficient_stock_items.append({
|
||||||
'product': cart_item.product_variant.product.name,
|
'product': cart_item.product_variant.product.name,
|
||||||
'variant': str(cart_item.product_variant),
|
'variant': str(cart_item.product_variant),
|
||||||
'requested': cart_item.quantity,
|
'requested': cart_item.quantity,
|
||||||
'available': cart_item.product_variant.in_stock
|
'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:
|
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({
|
return Response({
|
||||||
'error': 'موجودی برخی محصولات کافی نیست',
|
'detail': f'موجودی محصولات زیر کافی نیست: {product_list}',
|
||||||
'insufficient_items': insufficient_stock_items
|
'message': 'تعداد محصولات به صورت خودکار تنظیم شد',
|
||||||
|
'insufficient_items': insufficient_stock_items,
|
||||||
|
'adjusted_items': adjusted_items
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, 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:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@@ -268,11 +284,12 @@ class PaymentView(APIView):
|
|||||||
address=cart.address,
|
address=cart.address,
|
||||||
created_at=timezone.now().date(),
|
created_at=timezone.now().date(),
|
||||||
discount_code=cart.discount_code,
|
discount_code=cart.discount_code,
|
||||||
discount_amount=discount_amount,
|
discount_amount=cart.discount_code_amount,
|
||||||
tax=tax,
|
tax=cart.tax_amount,
|
||||||
final_price=final_price,
|
final_price=cart.final_price,
|
||||||
cart_total=cart_total,
|
cart_total=cart.cart_total,
|
||||||
status='ADMIN_PENDING'
|
status='ADMIN_PENDING',
|
||||||
|
cart=cart
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create order items and reduce product variant quantities
|
# Create order items and reduce product variant quantities
|
||||||
@@ -300,7 +317,7 @@ class PaymentView(APIView):
|
|||||||
|
|
||||||
bank = factory.create(bank_models.BankType.ZIBAL)
|
bank = factory.create(bank_models.BankType.ZIBAL)
|
||||||
bank.set_request(request)
|
bank.set_request(request)
|
||||||
bank.set_amount(final_price) # Use final_price instead of hardcoded amount
|
bank.set_amount(cart.final_price) # Use final_price instead of hardcoded amount
|
||||||
bank.set_client_callback_url('http://localhost:3000/transaction')
|
bank.set_client_callback_url('http://localhost:3000/transaction')
|
||||||
bank.set_mobile_number(user_mobile_number)
|
bank.set_mobile_number(user_mobile_number)
|
||||||
|
|
||||||
@@ -309,10 +326,7 @@ class PaymentView(APIView):
|
|||||||
bank_record.order = order
|
bank_record.order = order
|
||||||
bank_record.save()
|
bank_record.save()
|
||||||
|
|
||||||
# Clear cart after successful order creation
|
|
||||||
# cart.items.all().delete()
|
|
||||||
# cart.discount_code = None
|
|
||||||
# cart.save()
|
|
||||||
return Response({
|
return Response({
|
||||||
'url': bank.get_gateway()['url'],
|
'url': bank.get_gateway()['url'],
|
||||||
})
|
})
|
||||||
@@ -398,8 +412,11 @@ class CallbackView(APIView):
|
|||||||
return Response({'detail': 'کد تریسکد معتبر نمیباشد.'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'detail': 'کد تریسکد معتبر نمیباشد.'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if bank_record.is_success:
|
if bank_record.is_success:
|
||||||
|
bank_record.order.cart.clear_cart()
|
||||||
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)
|
||||||
|
else:
|
||||||
|
order = bank_record.order
|
||||||
|
order.rollback_stock()
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"detail": "پرداخت ناموفق بود. در صورت کسر وجه، مبلغ حداکثر تا ۴۸ ساعت آینده به حساب شما بازگردانده میشود.",
|
"detail": "پرداخت ناموفق بود. در صورت کسر وجه، مبلغ حداکثر تا ۴۸ ساعت آینده به حساب شما بازگردانده میشود.",
|
||||||
|
|||||||
Reference in New Issue
Block a user