Harden auth, AI tutor, payments, and subscriptions
This commit is contained in:
99
accounts/services.py
Normal file
99
accounts/services.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
منطق کمکیِ اپ Accounts که از views.py همین اپ صدا زده میشه.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OTPDeliveryError(APIException):
|
||||
"""
|
||||
⚠️ اصلاح: قبلاً send_otp_sms یک استاب بود که فقط لاگ میکرد و همیشه
|
||||
(بیقیدوشرط) به RequestOTPView «موفقیت» برمیگردوند؛ یعنی در پروداکشن
|
||||
(DEBUG=False، جایی که devOtp دیگه توی پاسخ برنمیگرده) هیچ پیامکِ
|
||||
واقعیای ارسال نمیشد ولی API همچنان success=True میداد — کاربرِ واقعی
|
||||
هیچوقت کدش رو نمیگرفت و حتی نمیفهمید که مشکلی پیش اومده. حالا اگه
|
||||
پیکربندیِ سرویسِ پیامک ناقص باشه یا خودِ درخواست به ارائهدهنده شکست
|
||||
بخوره، همین استثنا raise میشه و RequestOTPView یک خطای صریح (نه یک
|
||||
پاسخِ ساختگیِ موفق) برمیگردونه.
|
||||
"""
|
||||
|
||||
status_code = 503
|
||||
default_detail = 'در حال حاضر امکان ارسال پیامک OTP وجود ندارد؛ لطفاً بعداً دوباره تلاش کنید.'
|
||||
default_code = 'otp_sms_delivery_failed'
|
||||
|
||||
|
||||
def send_otp_sms(*, phone: str, code: str) -> None:
|
||||
"""
|
||||
ارسالِ واقعیِ پیامکِ OTP از طریق کاوهنگار (kavenegar.com) — دقیقاً همون
|
||||
ارائهدهندهای که در نسخهی قبلی (استاب) بهعنوان نمونه اشاره شده بود.
|
||||
کلید API هیچوقت داخل کد نیست؛ همیشه از env خونده میشه
|
||||
(settings.KAVENEGAR_API_KEY). دو حالت:
|
||||
|
||||
- DEBUG=True (dev/test): دقیقاً مثل قبل، فقط لاگ میکنه و هیچ پیامکِ
|
||||
واقعیای نمیفرسته — چون در این محیط، RequestOTPView خودِ کد رو در
|
||||
فیلدِ devOtp پاسخ برمیگردونه و SMS واقعی لازم نیست (رفتار قبلی dev
|
||||
حفظ شده).
|
||||
- DEBUG=False (production): اگه KAVENEGAR_API_KEY تنظیم نشده باشه، یا
|
||||
درخواست به کاوهنگار fail بشه/کدِ غیر ۲xx برگردونه، بهجای گزارشِ
|
||||
خاموشِ «موفقیت»، OTPDeliveryError raise میشه (کاربر پاسخ ۵۰۳ صریح
|
||||
میگیره، نه یک success دروغین).
|
||||
"""
|
||||
if settings.DEBUG:
|
||||
logger.info('OTP sms dispatch (dev, no real SMS sent): phone=%s code_len=%d', phone, len(code))
|
||||
return
|
||||
|
||||
if not settings.KAVENEGAR_API_KEY:
|
||||
logger.error('OTP SMS misconfigured: KAVENEGAR_API_KEY is not set (phone=%s)', phone)
|
||||
raise OTPDeliveryError('پیکربندیِ سرویسِ پیامک ناقص است.')
|
||||
|
||||
message = settings.OTP_SMS_TEMPLATE.format(code=code)
|
||||
payload = {'receptor': phone, 'message': message}
|
||||
if settings.KAVENEGAR_SENDER:
|
||||
payload['sender'] = settings.KAVENEGAR_SENDER
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f'https://api.kavenegar.com/v1/{settings.KAVENEGAR_API_KEY}/sms/send.json',
|
||||
data=payload,
|
||||
timeout=5,
|
||||
)
|
||||
except requests.RequestException:
|
||||
logger.exception('OTP SMS request to provider failed: phone=%s', phone)
|
||||
raise OTPDeliveryError('ارسال پیامک OTP با خطا مواجه شد.')
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(
|
||||
'OTP SMS provider returned non-200: phone=%s status=%s body=%s',
|
||||
phone, response.status_code, response.text[:500],
|
||||
)
|
||||
raise OTPDeliveryError('ارسال پیامک OTP با خطا مواجه شد.')
|
||||
|
||||
# ⚠️ اصلاح: کاوهنگار حتی برای شکستهای سطحِ اپلیکیشن (کلیدِ نامعتبر،
|
||||
# اعتبار ناکافی، گیرندهی نامعتبر و…) HTTP 200 برمیگردونه؛ نتیجهی
|
||||
# واقعی توی بدنهی JSON، زیرِ return.status هست (200 یعنی موفق). قبلاً
|
||||
# همینِ status_code == 200 بهتنهایی «موفقیت» در نظر گرفته میشد، یعنی
|
||||
# یک رد شدنِ واقعی از طرفِ کاوهنگار هم success=True دروغین برمیگردوند.
|
||||
# حالا بدنه parse میشه و فقط return.status == 200 موفقیت محسوب میشه؛
|
||||
# هر پاسخِ ناقص/غیرمنتظره هم شکستِ تحویل در نظر گرفته میشه (نه موفقیت).
|
||||
try:
|
||||
body = response.json()
|
||||
provider_status = body['return']['status']
|
||||
except (ValueError, KeyError, TypeError):
|
||||
logger.error(
|
||||
'OTP SMS provider returned malformed response body: phone=%s body=%s',
|
||||
phone, response.text[:500],
|
||||
)
|
||||
raise OTPDeliveryError('ارسال پیامک OTP با خطا مواجه شد.')
|
||||
|
||||
if provider_status != 200:
|
||||
logger.error(
|
||||
'OTP SMS provider rejected message at application level: phone=%s '
|
||||
'provider_status=%s body=%s',
|
||||
phone, provider_status, response.text[:500],
|
||||
)
|
||||
raise OTPDeliveryError('ارسال پیامک OTP با خطا مواجه شد.')
|
||||
Reference in New Issue
Block a user