- 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>
57 lines
3.8 KiB
Python
57 lines
3.8 KiB
Python
from rest_framework import serializers
|
||
|
||
|
||
class PlanSerializer(serializers.Serializer):
|
||
id = serializers.CharField()
|
||
title = serializers.CharField()
|
||
price = serializers.IntegerField()
|
||
currency = serializers.CharField(required=False)
|
||
popular = serializers.BooleanField()
|
||
discountPercent = serializers.IntegerField(required=False)
|
||
durationDays = serializers.IntegerField(required=False)
|
||
features = serializers.ListField(child=serializers.CharField())
|
||
badge = serializers.CharField(allow_blank=True)
|
||
aiDailyQuota = serializers.IntegerField(help_text='سوال در روز از دستیار برای مشترکِ این پلن.')
|
||
kind = serializers.ChoiceField(choices=['pack', 'subscription'])
|
||
questionCredits = serializers.IntegerField(help_text='بسته: سوالهایی که به اعتبار اضافه میشود (منقضی نمیشود).')
|
||
heartsDays = serializers.IntegerField(help_text='بسته: روزهای قلبِ نامحدودِ هدیه.')
|
||
bazaarSku = serializers.CharField(
|
||
allow_blank=True, help_text='SKUِ همین پلن (بسته یا اشتراک) در کافهبازار؛ خالی یعنی در بازار خریدنی نیست.')
|
||
|
||
|
||
class PlansListResponseSerializer(serializers.Serializer):
|
||
plans = PlanSerializer(many=True)
|
||
# تا کارتِ پلن بتواند بگوید «۵ سوال در روز (رایگان: ۳)» بیآنکه عدد در فرانت هاردکد شود.
|
||
freeAiDailyQuota = serializers.IntegerField()
|
||
|
||
|
||
class PurchaseRequestSerializer(serializers.Serializer):
|
||
# هدر Idempotency-Key هم روی همین درخواست لازمه (بخش ۴.۳ سند API)؛
|
||
# از request.headers خونده میشه، نه از بدنه.
|
||
planId = serializers.CharField()
|
||
# تا ۲۰۲۶-۱۰-۰۳ هر مقداری پذیرفته و نادیده گرفته میشد، پس «bazaar» بیصدا به زرینپال میرفت.
|
||
paymentMethod = serializers.ChoiceField(choices=['zarinpal', 'bazaar'], required=False, default='zarinpal')
|
||
|
||
|
||
class PurchaseResponseSerializer(serializers.Serializer):
|
||
orderId = serializers.CharField()
|
||
paymentUrl = serializers.CharField(
|
||
allow_null=True, help_text='زرینپال: آدرسی که کاربر به آن برود. بازار: null.')
|
||
bazaarSku = serializers.CharField(
|
||
required=False, help_text='فقط بازار: SKUی که اپِ اندروید با Poolakey میخرد؛ '
|
||
'developerPayload همان orderId باشد.')
|
||
bazaarProductType = serializers.ChoiceField(
|
||
choices=['inapp', 'subscription'], required=False,
|
||
help_text='فقط بازار: inapp (بسته؛ purchaseProduct و بعد consume) یا subscription (subscribeProduct، بیconsume).')
|
||
|
||
|
||
class SubscriptionStatusResponseSerializer(serializers.Serializer):
|
||
active = serializers.BooleanField(help_text='اشتراکِ زمانی فعال (یک/سه/ششماهه)؛ نه بسته.')
|
||
planTitle = serializers.CharField(allow_null=True, help_text='نامِ پلنِ اشتراکِ فعال؛ null بیاشتراک.')
|
||
planDurationDays = serializers.IntegerField(allow_null=True, help_text='طولِ دورهی پلنِ فعال به روز.')
|
||
aiDailyQuota = serializers.IntegerField(help_text='سقفِ سوالِ روزانهی همین کاربر از دستیار.')
|
||
planId = serializers.CharField(allow_null=True)
|
||
expiresAt = serializers.DateTimeField(allow_null=True)
|
||
questionCredits = serializers.IntegerField(help_text='سوالهای خریدهشدهی باقیمانده (منقضی نمیشوند).')
|
||
unlimitedHeartsUntil = serializers.DateTimeField(allow_null=True, help_text='پایانِ قلبِ نامحدودِ هدیهی بسته.')
|