Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
"""Payamak SMS provider adapter — only used inside Communication service."""
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
import httpx
|
|
|
|
from app.providers.contracts import (
|
|
BalanceResult,
|
|
ProviderHealth,
|
|
SendRequest,
|
|
SendResult,
|
|
)
|
|
|
|
|
|
class PayamakSMSProvider:
|
|
provider_kind = "payamak"
|
|
channel = "sms"
|
|
|
|
async def send(self, request: SendRequest, credentials: dict[str, Any]) -> SendResult:
|
|
username = credentials.get("username") or credentials.get("api_key")
|
|
password = credentials.get("password") or credentials.get("api_key")
|
|
from_number = request.from_address or credentials.get("from")
|
|
if not username or not password:
|
|
# Dev-safe path: simulate send when credentials absent
|
|
return SendResult(
|
|
success=True,
|
|
provider_message_id=f"payamak-dev-{uuid4()}",
|
|
raw_response={"mode": "dev_simulated"},
|
|
latency_ms=0,
|
|
)
|
|
|
|
started = time.perf_counter()
|
|
try:
|
|
async with httpx.AsyncClient(timeout=15.0) as client:
|
|
response = await client.post(
|
|
credentials.get(
|
|
"endpoint",
|
|
"https://api.payamak-panel.com/post/Send.asmx/SendSimpleSMS",
|
|
),
|
|
data={
|
|
"username": username,
|
|
"password": password,
|
|
"to": request.to_address,
|
|
"from": from_number or "",
|
|
"text": request.body,
|
|
"isflash": "false",
|
|
},
|
|
)
|
|
latency = int((time.perf_counter() - started) * 1000)
|
|
if response.status_code >= 400:
|
|
return SendResult(
|
|
success=False,
|
|
error_code="payamak_http_error",
|
|
error_message=response.text[:500],
|
|
latency_ms=latency,
|
|
raw_response={"status_code": response.status_code},
|
|
)
|
|
return SendResult(
|
|
success=True,
|
|
provider_message_id=response.text.strip()[:200] or f"payamak-{uuid4()}",
|
|
raw_response={"body": response.text[:500]},
|
|
latency_ms=latency,
|
|
)
|
|
except Exception as exc: # noqa: BLE001 — provider boundary
|
|
latency = int((time.perf_counter() - started) * 1000)
|
|
return SendResult(
|
|
success=False,
|
|
error_code="payamak_exception",
|
|
error_message=str(exc),
|
|
latency_ms=latency,
|
|
)
|
|
|
|
async def get_balance(self, credentials: dict[str, Any]) -> BalanceResult:
|
|
raw = credentials.get("cached_balance")
|
|
if raw is not None:
|
|
return BalanceResult(balance=float(raw), currency="IRR")
|
|
return BalanceResult(balance=None, currency="IRR")
|
|
|
|
async def health_check(self, credentials: dict[str, Any]) -> ProviderHealth:
|
|
if credentials.get("username") or credentials.get("api_key"):
|
|
return ProviderHealth(healthy=True, detail="credentials present")
|
|
return ProviderHealth(healthy=True, detail="dev mode without credentials")
|