Ilo/apps/accounts/services/sms.py
2026-05-02 20:01:30 +03:30

71 lines
2.3 KiB
Python

import logging
from typing import Protocol
import requests
from django.conf import settings
from django.utils.module_loading import import_string
logger = logging.getLogger(__name__)
class SMSError(Exception):
pass
class SMSBackend(Protocol):
def send_otp(self, phone_number: str, code: str) -> None: ...
class ConsoleSMSBackend:
"""Dev backend — logs the OTP instead of sending. Never use in production."""
def send_otp(self, phone_number: str, code: str) -> None:
logger.warning("[SMS-CONSOLE] OTP for %s: %s", phone_number, code)
class SMSIRBackend:
"""sms.ir template-based OTP via /v1/send/verify."""
def __init__(self):
self.api_key = settings.SMSIR_API_KEY
self.template_id = settings.SMSIR_TEMPLATE_ID
self.param_name = settings.SMSIR_TEMPLATE_PARAM_NAME
self.base_url = settings.SMSIR_BASE_URL.rstrip("/")
self.timeout = 15
if not self.api_key:
raise SMSError("SMSIR_API_KEY is not configured")
if not self.template_id:
raise SMSError("SMSIR_TEMPLATE_ID is not configured")
def send_otp(self, phone_number: str, code: str) -> None:
url = f"{self.base_url}/v1/send/verify"
headers = {"x-api-key": self.api_key, "Content-Type": "application/json"}
payload = {
"mobile": phone_number,
"templateId": int(self.template_id),
"parameters": [{"name": self.param_name, "value": str(code)}],
}
try:
resp = requests.post(url, json=payload, headers=headers, timeout=self.timeout)
except requests.RequestException as e:
raise SMSError(f"SMS network error: {e}") from e
try:
data = resp.json()
except ValueError as e:
raise SMSError(f"SMS non-JSON response (HTTP {resp.status_code})") from e
if not (200 <= resp.status_code < 300) or data.get("status") != 1:
msg = data.get("message") or f"HTTP {resp.status_code}"
raise SMSError(f"sms.ir error: {msg}")
logger.info("OTP delivered to %s via sms.ir template %s", phone_number, self.template_id)
_DEFAULT_BACKEND = "apps.accounts.services.sms.ConsoleSMSBackend"
def get_sms_backend() -> SMSBackend:
backend_path = getattr(settings, "SMS_BACKEND", _DEFAULT_BACKEND)
return import_string(backend_path)()