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