error-handling: add global exception handling, guard Bazaar API calls
This commit is contained in:
@@ -21,15 +21,29 @@ developers.cafebazaar.ir/fa/guidelines/in-app-billing/api/postman-and-bazaar-api
|
||||
fail-closed عمل میکنه و invalid برمیگردونه؛ هیچوقت به purchaseToken
|
||||
ارسالیِ کلاینت، بدون صحتسنجیِ واقعی با سرور بازار، اعتماد نمیشه.
|
||||
"""
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ACCESS_TOKEN_CACHE_KEY = 'bazaar_iap_access_token'
|
||||
|
||||
|
||||
class BazaarAPIError(APIException):
|
||||
"""خطای موقتِ سرویسِ بازار (شبکه/HTTP/پاسخِ نامعتبر). عمداً is_valid=False
|
||||
برنمیگردونیم چون نبودِ پاسخِ قابلاعتماد بهمعنیِ خریدِ نامعتبر نیست —
|
||||
ممکنه یک خریدِ واقعاً معتبر رو غلط failed کنه."""
|
||||
|
||||
status_code = 503
|
||||
default_detail = 'در حال حاضر امکان تایید خرید با سرور بازار وجود ندارد؛ لطفاً بعداً دوباره تلاش کنید.'
|
||||
default_code = 'bazaar_api_unavailable'
|
||||
|
||||
|
||||
@dataclass
|
||||
class BazaarPurchaseResult:
|
||||
is_valid: bool
|
||||
@@ -58,19 +72,29 @@ def _get_access_token() -> str:
|
||||
# برمیگرده — ولی برای دفاع در عمق، اینجا هم صراحتاً خطا میده.
|
||||
raise RuntimeError('BAZAAR_REFRESH_TOKEN is not configured; cannot obtain a Bazaar 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']
|
||||
try:
|
||||
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()
|
||||
except requests.RequestException:
|
||||
logger.exception('Bazaar access-token request failed')
|
||||
raise BazaarAPIError()
|
||||
|
||||
try:
|
||||
data = resp.json()
|
||||
token = data['access_token']
|
||||
except (ValueError, KeyError, TypeError):
|
||||
logger.error('Bazaar access-token response malformed: status=%s', resp.status_code)
|
||||
raise BazaarAPIError()
|
||||
|
||||
# طبق سند بازار access_token معمولاً ~۱ ساعت اعتباره؛ کمی زودتر منقضی
|
||||
# میکنیم تا لبهی زمانی مشکل نسازه.
|
||||
cache.set(ACCESS_TOKEN_CACHE_KEY, token, timeout=int(data.get('expires_in', 3600)) - 60)
|
||||
@@ -102,11 +126,21 @@ def verify_purchase(*, product_sku: str, purchase_token: str, is_subscription: b
|
||||
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)
|
||||
try:
|
||||
resp = requests.get(url, headers={'Authorization': f'Bearer {access_token}'}, timeout=10)
|
||||
except requests.RequestException:
|
||||
logger.exception('Bazaar verify-purchase request failed')
|
||||
raise BazaarAPIError()
|
||||
|
||||
if resp.status_code != 200:
|
||||
return BazaarPurchaseResult(is_valid=False, raw={'status_code': resp.status_code})
|
||||
|
||||
data = resp.json()
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError:
|
||||
logger.error('Bazaar verify-purchase response malformed: status=%s', resp.status_code)
|
||||
raise BazaarAPIError()
|
||||
|
||||
purchase_state = data.get('purchaseState')
|
||||
return BazaarPurchaseResult(
|
||||
is_valid=purchase_state == 0, # ۰ یعنی خریداریشده و معتبر
|
||||
|
||||
Reference in New Issue
Block a user