Harden auth, AI tutor, payments, and subscriptions
This commit is contained in:
@@ -8,22 +8,12 @@ from .models import Subscription
|
||||
|
||||
def activate_subscription(subscription_id: int):
|
||||
"""
|
||||
بعد از تایید پرداخت (payment webhook) صدا زده میشه؛ اشتراک رو active
|
||||
میکنه و expires_at رو بر اساس Plan.duration_days محاسبه میکنه.
|
||||
(قبلاً چون Plan فیلد durationای نداشت، این مقدار با یک قاعدهی stub
|
||||
بر اساس متن title حدس زده میشد؛ الان duration_days مستقیماً از مدل
|
||||
Plan خونده میشه.)
|
||||
|
||||
⚛️ atomic: خودِ این تابع هم مستقل (بدون اتکا به atomic بودنِ caller)
|
||||
اتمیکه، چون بهعنوان یک سرویسِ مشترک از جاهای مختلف (payment webhook،
|
||||
bazaar verify) صدا زده میشه؛ save روی Subscription و نوشتنِ
|
||||
StatusChangeLog که از signal.send نتیجه میشه، باید یک واحد بمونن.
|
||||
بعد از تایید پرداخت (payment webhook) صدا زده میشود؛ اشتراک را active
|
||||
میکند و expires_at را بر اساس Plan.duration_days محاسبه میکند.
|
||||
مستقل از caller اتمیک است چون از چند جا (payment webhook، bazaar verify) صدا زده میشود.
|
||||
"""
|
||||
with transaction.atomic():
|
||||
try:
|
||||
# 🔒 select_for_update اضافه شد تا این تابع هم مثل
|
||||
# grant_free_subscription، صرفنظر از caller، در برابر
|
||||
# concurrent update ایمن باشه.
|
||||
subscription = Subscription.objects.select_for_update().get(pk=subscription_id)
|
||||
except Subscription.DoesNotExist:
|
||||
return None
|
||||
@@ -44,24 +34,11 @@ def activate_subscription(subscription_id: int):
|
||||
|
||||
def grant_free_subscription(user_id: int, *, duration_days: int | None = None, event: str = 'offer_applied'):
|
||||
"""
|
||||
فعالسازی اشتراک بدون هیچ Order/پرداختی — برای Offer (آفر رایگان
|
||||
محدود/کامل). duration_days=None یعنی نامحدود (free_full)؛ عدد یعنی
|
||||
trial با همون تعداد روز.
|
||||
فعالسازی اشتراک بدون هیچ Order/پرداختی — برای Offer (آفر رایگان محدود/کامل).
|
||||
duration_days=None یعنی نامحدود (free_full)؛ عدد یعنی trial با همان تعداد روز.
|
||||
|
||||
از discount/services.py (apply_offer) صدا زده میشه؛ مثل بقیهی
|
||||
ارتباطات بیناپیِ این پروژه، یک import مستقیم به تابع سرویس، نه FK
|
||||
واقعی و نه HTTP call به خودمون.
|
||||
|
||||
⚛️ atomic: مثل activate_subscription، مستقل از caller اتمیکه — چون
|
||||
get_or_create + save + signal.send (که StatusChangeLog میسازه) باید
|
||||
یک واحد باشن، چه apply_offer از قبل توی atomic باشه چه نه.
|
||||
|
||||
⚛️🔒 اصلاح race condition: قبلاً بعد از get_or_create هیچ قفلی گرفته
|
||||
نمیشد؛ دو فراخوانیِ همزمان (مثلاً دو آفر که تقریباً همزمان اعمال
|
||||
میشن) میتونستن هر دو همون Subscription رو بخونن، هر دو expires_at
|
||||
رو بر اساس همون old_status/duration محاسبه کنن و save کنن — یکی از
|
||||
دو نتیجه گم میشد (lost update). حالا ردیفِ Subscription قبل از
|
||||
خوندن/تغییرش با select_for_update قفل میشه.
|
||||
مستقل از caller اتمیک است. ردیف Subscription قبل از خواندن/تغییر با
|
||||
select_for_update قفل میشود تا فراخوانیهای همزمان lost update نسازند.
|
||||
"""
|
||||
with transaction.atomic():
|
||||
Subscription.objects.get_or_create(user_id=user_id)
|
||||
|
||||
Reference in New Issue
Block a user