100 lines
5.4 KiB
Python
100 lines
5.4 KiB
Python
"""
|
|
منطق کمکیِ اپ 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 با خطا مواجه شد.')
|