Files
MathApp/payment/serializers.py
Zareian 8077ab4350 Bazaar: subscriptions (10343/10344), dynamic price (discount codes on Bazaar), auto-renewal sync
- Purchase with paymentMethod=bazaar now works for subscription plans with a bazaar_sku (not only packs);
  response carries bazaarProductType. Migration subscription.0014 puts 10343/10344 on the 90/180-day plans.
- Verify asks Bazaar for the right product type (from the order) and stores validUntil (payment.0012).
- POST /api/payment/bazaar/orders/{id}/start: SKU, type, and an HS256 dynamic-price JWT when a discount
  code lowered the price (BAZAAR_DYNAMIC_PRICE_KEY); discount on Bazaar orders allowed only with that key
  and only before start.
- Auto-renewal: sync_bazaar_renewals re-asks Bazaar when the paid period is near its end (status page and
  the paywall via has_subscription_access); each new period becomes a paid order.
- Default BAZAAR_PACKAGE_NAME = com.tadrisino.math.app; health shows bazaarDynamicPrice.
979 tests green. No real Bazaar purchase or renewal tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:53:11 +03:30

93 lines
5.0 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 یعنی قیمتِ پیشخان.')
class BazaarVerifyResponseSerializer(serializers.Serializer):
orderId = serializers.CharField()
status = serializers.CharField()
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."""
orderId = serializers.CharField()
status = serializers.CharField()
class TransactionSerializer(serializers.Serializer):
id = serializers.CharField()
gateway = serializers.CharField()
status = serializers.CharField()
paidAt = serializers.DateTimeField(allow_null=True)
class OrderDetailResponseSerializer(serializers.Serializer):
orderId = serializers.CharField()
status = serializers.CharField()
amount = serializers.IntegerField()
transactions = TransactionSerializer(many=True)
class InvoiceSerializer(serializers.Serializer):
orderId = serializers.CharField()
invoiceNumber = serializers.CharField(help_text='شماره‌ی فاکتور: «TD-» و شناسه‌ی سفارش، شش رقمی.')
status = serializers.ChoiceField(choices=['pending', 'paid', 'failed', 'canceled', 'refunded'])
provider = serializers.CharField(help_text='gateway (زرین‌پال) یا bazaar')
planTitle = serializers.CharField(allow_blank=True)
listPrice = serializers.IntegerField(allow_null=True, help_text='قیمتِ پایه پیش از تخفیف؛ null برای سفارش‌های پیش از ۱۴۰۵-۰۷-۰۹.')
discountAmount = serializers.IntegerField(allow_null=True)
discountCode = serializers.CharField(allow_blank=True)
amount = serializers.IntegerField(help_text='مبلغِ پرداختی (پس از تخفیف).')
currency = serializers.CharField()
createdAt = serializers.DateTimeField()
paidAt = serializers.DateTimeField(allow_null=True)
refId = serializers.CharField(allow_blank=True, help_text='کدِ پیگیریِ پرداخت (شماره‌ی مرجعِ زرین‌پال/بازار).')
class InvoiceListResponseSerializer(serializers.Serializer):
invoices = InvoiceSerializer(many=True)