Update project

This commit is contained in:
2026-08-19 17:15:55 +03:30
parent 02d40e1238
commit f945f960c9
25 changed files with 1092 additions and 287 deletions

View File

@@ -98,13 +98,24 @@ class RedeemDiscountView(APIView):
if discount.redemptions.filter(user_id=request.user.id).exists():
raise ValidationError({'code': 'شما قبلاً این کد را استفاده کرده‌اید.'})
# فقط سفارش‌های در انتظار پرداخت می‌تونن تخفیف بگیرن؛ سفارش
# پرداخت‌شده/ناموفق نباید عقب‌گرد تغییر کنه.
if order.status != 'pending':
raise ValidationError({'code': 'این سفارش دیگر قابل اعمال تخفیف نیست.'})
# ⚛️🔒 اصلاح race condition: قبلاً چکِ «order.status != 'pending'»
# روی همون آبجکتِ order ای انجام می‌شد که *قبل* از ورود به این
# atomic block و *بدون* قفل خونده شده بود (چند خط بالاتر، برای
# چکِ مالکیت). یعنی اگه بینِ اون خواندنِ اولیه و این چک، یک
# webhookِ پرداختِ هم‌زمان روی همون سفارش paid می‌شد (که خودش با
# select_for_update قفل می‌گیره)، این ویو همچنان مقدارِ
# cached/قدیمیِ status='pending' رو می‌دید و اجازه می‌داد مبلغِ
# یک سفارشِ *پرداخت‌شده* بعد از پرداخت واقعی کم بشه — دقیقاً
# همون کلاسِ باگی که apply_order_payment_result در
# payment/services.py با قفل‌گرفتن *قبل* از چکِ already_paid
# حلش کرده. حالا سفارش اول قفل می‌شه (select_for_update) و
# چکِ pending بودن *بعد* از قفل، روی خودِ ردیفِ قفل‌شده
# (order_locked) انجام می‌شه.
order_locked = Order.objects.select_for_update().get(pk=order.id)
if order_locked.status != 'pending':
raise ValidationError({'code': 'این سفارش دیگر قابل اعمال تخفیف نیست.'})
if discount.type == 'percent':
discount_amount = order_locked.amount * discount.value // 100
else: