Paywall: first unit of each grade free; 1/3/6-month subscriptions; AI sold separately
- content: unit_is_free/has_unit_access; questions, node quiz, answer and lesson/node complete return 402 subscription_required outside the first unit without an active subscription. /api/home units carry requiresSubscription. - subscription.0013: monthly 498k, quarterly 798k, six-month 1,298k toman, unlimited hearts, 3 AI questions/day; packs stay on sale for more questions. - AI_DAILY_QUOTA_FREE default 3 -> 2. - /api/subscription/status adds planTitle, planDurationDays, aiDailyQuota. - tests: content/tests_paywall.py; existing unlock-order tests get a subscription. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ from .serializers import (
|
||||
PurchaseResponseSerializer,
|
||||
SubscriptionStatusResponseSerializer,
|
||||
)
|
||||
from .services import credit_balance, has_active_subscription, hearts_pass_until
|
||||
from .services import ai_daily_quota, credit_balance, has_active_subscription, hearts_pass_until
|
||||
|
||||
|
||||
# پلنها تقریباً هیچوقت عوض نمیشوند و روی صفحهی اشتراک هر بار خوانده میشوند.
|
||||
@@ -104,8 +104,8 @@ class PurchaseView(APIView):
|
||||
idempotency_key = request.headers.get('Idempotency-Key')
|
||||
|
||||
if v['paymentMethod'] == 'bazaar':
|
||||
# فقط بسته: اشتراکها دیگر فروخته نمیشوند و مسیرِ اشتراکِ بازار (تمدیدِ خودکار) هرگز
|
||||
# با خریدِ واقعی آزموده نشده. SKU از پلن، نه از کلاینت.
|
||||
# فقط بسته: مسیرِ اشتراکِ بازار (تمدیدِ خودکار) هرگز با خریدِ واقعی آزموده نشده، پس
|
||||
# اشتراک فعلاً فقط از زرینپال فروخته میشود. SKU از پلن، نه از کلاینت.
|
||||
if plan.kind != 'pack':
|
||||
raise ValidationError({'paymentMethod': 'خرید از بازار فقط برای بستههای سوال است.'})
|
||||
if not plan.bazaar_sku:
|
||||
@@ -229,11 +229,17 @@ class SubscriptionStatusView(APIView):
|
||||
def get(self, request):
|
||||
# پرداختی که تأییدش در کالبک گم شده، اینجا دوباره سنجیده میشود.
|
||||
reconcile_user_orders(request.user.id)
|
||||
subscription = Subscription.objects.filter(user_id=request.user.id).first()
|
||||
subscription = Subscription.objects.select_related('plan').filter(user_id=request.user.id).first()
|
||||
active = has_active_subscription(request.user.id)
|
||||
data = {
|
||||
# وضعیتِ «active» با گذشتِ تاریخ خودبهخود عوض نمیشود؛ همان شرطی که
|
||||
# قلبِ نامحدود را میدهد ملاک است، وگرنه اشتراکِ منقضی «فعال» دیده میشد.
|
||||
'active': has_active_subscription(request.user.id),
|
||||
'active': active,
|
||||
'planTitle': subscription.plan.title if active and subscription.plan else None,
|
||||
# طولِ دورهی پلن، برای نوارِ «چقدر مانده» در پروفایل. تمدیدِ زودتر از موعد روزها را روی هم
|
||||
# میگذارد، پس روزِ مانده میتواند از این بیشتر باشد؛ فرانت نوار را در ۱۰۰٪ میبندد.
|
||||
'planDurationDays': subscription.plan.duration_days if active and subscription.plan else None,
|
||||
'aiDailyQuota': ai_daily_quota(request.user.id),
|
||||
'planId': str(subscription.plan_id) if subscription and subscription.plan_id else None,
|
||||
'expiresAt': subscription.expires_at if subscription else None,
|
||||
'questionCredits': credit_balance(request.user.id),
|
||||
|
||||
Reference in New Issue
Block a user