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>
This commit is contained in:
@@ -103,20 +103,29 @@ class PurchaseView(APIView):
|
||||
plan = get_object_or_404(Plan, pk=v['planId'], on_sale=True)
|
||||
idempotency_key = request.headers.get('Idempotency-Key')
|
||||
|
||||
# بازار: SKU از پلن، نه از کلاینت. بسته محصولِ مصرفشدنیِ بازار است و اشتراک محصولِ «اشتراکی»
|
||||
# (از ۲۰۲۶-۱۰-۱۲؛ تمدیدِ خودکارش در payment.services.sync_bazaar_renewals). خریدِ واقعی هنوز آزموده نشده.
|
||||
provider = 'gateway'
|
||||
if v['paymentMethod'] == 'bazaar':
|
||||
# فقط بسته: مسیرِ اشتراکِ بازار (تمدیدِ خودکار) هرگز با خریدِ واقعی آزموده نشده، پس
|
||||
# اشتراک فعلاً فقط از زرینپال فروخته میشود. SKU از پلن، نه از کلاینت.
|
||||
if plan.kind != 'pack':
|
||||
raise ValidationError({'paymentMethod': 'خرید از بازار فقط برای بستههای سوال است.'})
|
||||
if not plan.bazaar_sku:
|
||||
raise ValidationError({'planId': 'این بسته هنوز در کافهبازار تعریف نشده است.'})
|
||||
raise ValidationError({'planId': 'این مورد هنوز در کافهبازار تعریف نشده است.'})
|
||||
provider = 'bazaar'
|
||||
bazaar_sku = plan.bazaar_sku if provider == 'bazaar' else ''
|
||||
|
||||
def response(order, payment_url):
|
||||
data = {'orderId': str(order.id), 'paymentUrl': payment_url}
|
||||
if provider == 'bazaar':
|
||||
data.update(bazaarSku=order.bazaar_product_sku,
|
||||
bazaarProductType='subscription' if plan.kind == 'subscription' else 'inapp')
|
||||
return Response(PurchaseResponseSerializer(data).data)
|
||||
|
||||
if provider == 'bazaar' and plan.kind == 'pack':
|
||||
order, _ = create_order(
|
||||
user_id=user.id, amount=plan.price, currency=plan.currency,
|
||||
idempotency_key=idempotency_key, plan_id=plan.id, plan_title=plan.title,
|
||||
provider='bazaar', bazaar_product_sku=plan.bazaar_sku,
|
||||
provider='bazaar', bazaar_product_sku=bazaar_sku,
|
||||
)
|
||||
return Response(PurchaseResponseSerializer(
|
||||
{'orderId': str(order.id), 'paymentUrl': None, 'bazaarSku': order.bazaar_product_sku}).data)
|
||||
return response(order, None)
|
||||
|
||||
if plan.kind == 'pack':
|
||||
# بسته به Subscription دست نمیزند: اعتبار و روزهای قلب بعد از پرداخت از روی
|
||||
@@ -187,6 +196,8 @@ class PurchaseView(APIView):
|
||||
idempotency_key=idempotency_key,
|
||||
plan_id=plan.id,
|
||||
plan_title=plan.title,
|
||||
provider=provider,
|
||||
bazaar_product_sku=bazaar_sku,
|
||||
)
|
||||
except Exception:
|
||||
# Guard by pk+status+plan_id+order_id+version so this cleanup only
|
||||
@@ -212,8 +223,7 @@ class PurchaseView(APIView):
|
||||
subscription.order_id = order.id
|
||||
subscription.save(update_fields=['order_id'])
|
||||
|
||||
data = {'orderId': str(order.id), 'paymentUrl': payment_url}
|
||||
return Response(PurchaseResponseSerializer(data).data)
|
||||
return response(order, payment_url)
|
||||
|
||||
|
||||
class SubscriptionStatusView(APIView):
|
||||
|
||||
Reference in New Issue
Block a user