import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import axios from 'axios'; import { normalizeMobile } from '../common/mobile.util'; export type CentralSmsResult = { success: boolean; raw?: unknown; error?: string; }; @Injectable() export class CentralSmsService { private readonly logger = new Logger(CentralSmsService.name); constructor(private readonly config: ConfigService) {} async send(phone: string, text: string): Promise { const to = normalizeMobile(phone); const message = text.trim(); if (!to) { return { success: false, error: 'شماره موبایل نامعتبر است' }; } if (!message) { return { success: false, error: 'متن پیامک خالی است' }; } const url = this.config.get('CENTRAL_SMS_URL') || 'http://185.231.112.101/api/createsms'; const secret = this.config.get('CENTRAL_SMS_SECRET')?.trim(); if (!secret) { this.logger.error('CENTRAL_SMS_SECRET is not configured'); return { success: false, error: 'سرویس پیامک پیکربندی نشده است' }; } const host = this.config.get('CENTRAL_SMS_HOST') || 'superadmin'; const platform = this.config.get('CENTRAL_SMS_PLATFORM') || 'superadmin'; try { const response = await axios.post( url, { phone: to, host, text: message, platform, }, { timeout: 15000, headers: { 'Content-Type': 'application/json', secret_key: secret, }, // Prefer IPv4 like Hamkar curl CURLOPT_IPRESOLVE_V4 family: 4, validateStatus: () => true, }, ); const body = response.data; let success = response.status >= 200 && response.status < 300; if (body && typeof body === 'object') { if (body.success === false || body.ok === false) success = false; const status = String(body.status ?? '').toLowerCase(); if (['error', 'failed', 'fail'].includes(status)) success = false; } if (!success) { this.logger.warn(`Central SMS failed to ${to} (HTTP ${response.status})`); } return { success, raw: body }; } catch (error: any) { const msg = error?.message || 'SMS send error'; this.logger.error(`Central SMS exception to ${to}: ${msg}`); return { success: false, error: msg }; } } }