Question packs removed at the root; all subscriptions 4 assistant questions a day; Bazaar is subscription-only

- subscription.0017/0018, payment.0013: pack plans deleted, QuestionCredit/HeartsPass models and Plan.kind /
  question_credits / hearts_days / Order.question_credits / hearts_days dropped. The only paid pack order was the
  company owner's test account (user 26).
- Assistant: no credit spending; quota is the daily cap only (free 2, every plan 4, gift subscription 4).
- Bazaar: verify_purchase is subscription-only (no inapp guess), start/purchase responses lose productType,
  verify refuses an order without a subscription. A paid order with no subscription now logs ERROR.
- Removed POST /api/payment/orders (internal; no caller). Its permission tests stay.
- Pack-based tests rewritten on subscriptions (refund, security, Bazaar token); pack-only test files deleted.
- docs: profit table for 4/day on both payment routes plus free users; handbook, operations, CLAUDE.md current.
949 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 20:33:38 +03:30
parent 9c3adc3e60
commit 947f946909
39 changed files with 398 additions and 1174 deletions

View File

@@ -1,36 +1,14 @@
from rest_framework import serializers
class CreateOrderRequestSerializer(serializers.Serializer):
# این فراخوانی سرور-به-سرور از اپ Subscription‌ه، نه از کلاینت؛ به همین
# دلیل userId اینجا مستقیماً در بدنه ارسال می‌شه (نه از توکن استخراج).
userId = serializers.CharField()
amount = serializers.IntegerField()
currency = serializers.CharField(required=False, default='IRT')
subscriptionId = serializers.CharField(required=False, allow_null=True, allow_blank=True)
# provider='bazaar' وقتی کاربر از همون ابتدا پرداخت درون‌برنامه‌ای بازار
# رو انتخاب کرده؛ در این حالت amount باید دقیقاً برابر قیمتِ محصولِ
# تعریف‌شده در پنل بازار برای همون bazaarProductSku باشه (بازار خودش
# قیمت رو کنترل می‌کنه، نه ما).
provider = serializers.ChoiceField(choices=['gateway', 'bazaar'], required=False, default='gateway')
bazaarProductSku = serializers.CharField(required=False, allow_blank=True)
# بسته‌ی سوال (subscription.Plan با kind='pack'): بدونِ این، سفارشِ بسته بی‌plan_id ساخته می‌شد و
# پرداختش هیچ اعتباری نمی‌داد (بازبینیِ ۲۰۲۶-۱۰-۰۲).
planId = serializers.CharField(required=False, allow_null=True, allow_blank=True)
class BazaarVerifyRequestSerializer(serializers.Serializer):
orderId = serializers.CharField()
productSku = serializers.CharField(required=False, allow_blank=True)
purchaseToken = serializers.CharField()
class BazaarStartResponseSerializer(serializers.Serializer):
orderId = serializers.CharField()
sku = serializers.CharField(help_text='productIdِ PurchaseRequestِ Poolakey.')
productType = serializers.ChoiceField(
choices=['inapp', 'subscription'],
help_text='inapp: purchaseProduct و بعد از تأییدِ سرور consume. subscription: subscribeProduct، بی‌consume.')
amount = serializers.IntegerField(help_text='مبلغِ سفارش (تومان)، پس از کدِ تخفیف.')
dynamicPriceToken = serializers.CharField(
allow_null=True, help_text='JWTِ «تخفیف پویا» برای PurchaseRequest.dynamicPriceToken؛ null یعنی قیمتِ پیشخان.')
@@ -42,15 +20,6 @@ class BazaarVerifyResponseSerializer(serializers.Serializer):
valid = serializers.BooleanField()
class CreateOrderResponseSerializer(serializers.Serializer):
orderId = serializers.CharField()
status = serializers.CharField()
# provider='bazaar' یعنی paymentUrl نداریم (کلاینت با SDK بازار خودش
# خرید می‌کنه، نه با ریدایرکت به یک URL).
paymentUrl = serializers.CharField(allow_null=True)
provider = serializers.CharField()
class ZarinpalCallbackResponseSerializer(serializers.Serializer):
"""فقط وقتی settings.ZARINPAL_REDIRECT_URL خالی باشه برگردونده می‌شه؛
وگرنه کاربر به همون URL ریدایرکت می‌شه، نه به این JSON."""