""" کلاینت سرور-به-سرورِ پرداخت درون‌برنامه‌ای بازار (REST API v2)، طبق داکیومنت رسمی: developers.cafebazaar.ir/fa/guidelines/in-app-billing جریان رسمی: ۱) یک‌بار (دستی، از پنل devapi توسعه‌دهنده) با CLIENT_ID/CLIENT_SECRET یک authorization code گرفته می‌شه و با اون REFRESH_TOKEN ساخته می‌شه — این مرحله در این فایل نیست چون یک‌بار و دستی‌ست، نه بخشی از API عمومی. ۲) بک‌اند با همون REFRESH_TOKEN، هر بار (یا هر بار که access_token منقضی شده) یک ACCESS_TOKEN تازه می‌گیره (POST /auth/token با grant_type=refresh_token). ۳) با همون access_token، وضعیت خرید/اشتراک از endpoint purchase/subscription خونده می‌شه (GET .../applications/{package}/inapp/{sku}/purchases/{token}/). ⚠️ کافه‌بازار روش صحت‌سنجی رو تغییر داده (نگاه کنید به developers.cafebazaar.ir/fa/guidelines/in-app-billing/api/postman-and-bazaar-api-new)؛ قبل از اتصال واقعی به production، دقیق‌ترین مسیر endpoint را از همون صفحه یا پنل devapi خودتون تایید کنید — این فایل فقط اسکلتِ همون جریان رسمی رو پیاده‌سازی کرده تا بدون کلید واقعی هم (مثل call_payment_gateway در services.py) از طریق Swagger قابل تست باشه. """ from dataclasses import dataclass import requests from django.conf import settings from django.core.cache import cache ACCESS_TOKEN_CACHE_KEY = 'bazaar_iap_access_token' @dataclass class BazaarPurchaseResult: is_valid: bool purchase_state: int | None = None # ۰=خریداری‌شده، ۱=لغوشده (طبق سند بازار) consumption_state: int | None = None developer_payload: str = '' raw: dict | None = None def _get_access_token() -> str: """ ACCESS_TOKEN رو کش می‌کنه (توی cache framework جنگو) تا هر verify یک درخواست OAuth جدا نزنه؛ اگه کش نداشت یا منقضی شده بود، با REFRESH_TOKEN یکی تازه می‌گیره. """ cached = cache.get(ACCESS_TOKEN_CACHE_KEY) if cached: return cached if not settings.BAZAAR_REFRESH_TOKEN: # بدون کلید واقعی (مثلاً محیط توسعه)، استاب: دقیقاً مثل # call_payment_gateway در payment/services.py، تا جریان کامل بدون # کلید واقعی بازار هم قابل تست باشه. return 'stub-access-token' resp = requests.post( f'{settings.BAZAAR_API_BASE}/auth/token/', data={ 'grant_type': 'refresh_token', 'client_id': settings.BAZAAR_CLIENT_ID, 'client_secret': settings.BAZAAR_CLIENT_SECRET, 'refresh_token': settings.BAZAAR_REFRESH_TOKEN, }, timeout=10, ) resp.raise_for_status() data = resp.json() token = data['access_token'] # طبق سند بازار access_token معمولاً ~۱ ساعت اعتباره؛ کمی زودتر منقضی # می‌کنیم تا لبه‌ی زمانی مشکل نسازه. cache.set(ACCESS_TOKEN_CACHE_KEY, token, timeout=int(data.get('expires_in', 3600)) - 60) return token def verify_purchase(*, product_sku: str, purchase_token: str, is_subscription: bool = False) -> BazaarPurchaseResult: """ صحت‌سنجی purchaseToken که کلاینت اندروید بعد از خرید از SDK بازار گرفته. is_subscription=True برای اشتراک‌های دوره‌ای بازار (endpoint جدا از خرید تک‌محصولی)، طبق سند رسمی. """ if not settings.BAZAAR_REFRESH_TOKEN: # استاب توسعه: هر token غیرخالی معتبر فرض می‌شه تا کل جریان # (ساخت سفارش bazaar → verify → فعال‌سازی اشتراک) بدون کلید واقعی # از طریق Swagger قابل تست باشه. return BazaarPurchaseResult(is_valid=bool(purchase_token), purchase_state=0) access_token = _get_access_token() kind = 'subscriptions' if is_subscription else 'purchases' url = ( f'{settings.BAZAAR_API_BASE}/api/applications/' f'{settings.BAZAAR_PACKAGE_NAME}/{kind}/{product_sku}/{purchase_token}/' ) resp = requests.get(url, headers={'Authorization': f'Bearer {access_token}'}, timeout=10) if resp.status_code != 200: return BazaarPurchaseResult(is_valid=False, raw={'status_code': resp.status_code}) data = resp.json() purchase_state = data.get('purchaseState') return BazaarPurchaseResult( is_valid=purchase_state == 0, # ۰ یعنی خریداری‌شده و معتبر purchase_state=purchase_state, consumption_state=data.get('consumptionState'), developer_payload=data.get('developerPayload', ''), raw=data, )