diff --git a/src/Meezi.API/Services/ConsumerAuthService.cs b/src/Meezi.API/Services/ConsumerAuthService.cs index 2a63421..70d9282 100644 --- a/src/Meezi.API/Services/ConsumerAuthService.cs +++ b/src/Meezi.API/Services/ConsumerAuthService.cs @@ -90,9 +90,6 @@ public class ConsumerAuthService : IConsumerAuthService var otp = Random.Shared.Next(100000, 999999).ToString(); await redis.StringSetAsync($"consumer-otp:{phone}", otp, TimeSpan.FromSeconds(OtpTtlSeconds)); - if (string.IsNullOrWhiteSpace(_configuration["Kavenegar:ApiKey"])) - _logger.LogWarning("DEV consumer OTP for {Phone}: {Otp}", phone, otp); - try { await _smsService.SendOtpAsync(phone, otp, cancellationToken); diff --git a/src/Meezi.Admin.API/Services/AdminAuthService.cs b/src/Meezi.Admin.API/Services/AdminAuthService.cs index 32b3e88..7095ae2 100644 --- a/src/Meezi.Admin.API/Services/AdminAuthService.cs +++ b/src/Meezi.Admin.API/Services/AdminAuthService.cs @@ -79,9 +79,6 @@ public class AdminAuthService : IAdminAuthService var otp = Random.Shared.Next(100000, 999999).ToString(); await redis.StringSetAsync($"otp:admin:{phone}", otp, TimeSpan.FromSeconds(OtpTtlSeconds)); - if (string.IsNullOrWhiteSpace(_configuration["Kavenegar:ApiKey"])) - _logger.LogWarning("DEV admin OTP for {Phone}: {Otp}", phone, otp); - try { await _smsService.SendOtpAsync(phone, otp, cancellationToken); diff --git a/src/Meezi.Infrastructure/ExternalServices/KavenegarSmsService.cs b/src/Meezi.Infrastructure/ExternalServices/KavenegarSmsService.cs index 94163b3..cc16d29 100644 --- a/src/Meezi.Infrastructure/ExternalServices/KavenegarSmsService.cs +++ b/src/Meezi.Infrastructure/ExternalServices/KavenegarSmsService.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Meezi.Core.Interfaces; using Meezi.Infrastructure.Services.Platform; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Meezi.Infrastructure.ExternalServices; @@ -26,17 +27,20 @@ public class KavenegarSmsService : ISmsService private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; private readonly IPlatformRuntimeConfig _platform; + private readonly IHostEnvironment _environment; private readonly ILogger _logger; public KavenegarSmsService( HttpClient httpClient, IConfiguration configuration, IPlatformRuntimeConfig platform, + IHostEnvironment environment, ILogger logger) { _httpClient = httpClient; _configuration = configuration; _platform = platform; + _environment = environment; _logger = logger; } @@ -44,6 +48,12 @@ public class KavenegarSmsService : ISmsService public async Task SendOtpAsync(string phone, string otp, CancellationToken cancellationToken = default) { + if (_environment.IsDevelopment()) + { + _logger.LogWarning("[DEV OTP] {Phone}: {Otp}", phone, otp); + return; // Skip real SMS in development — read OTP from logs + } + var (apiKey, _, template) = await GetConfigAsync(cancellationToken); if (string.IsNullOrWhiteSpace(apiKey)) { @@ -54,7 +64,7 @@ public class KavenegarSmsService : ISmsService var url = $"{BaseUrl}/{apiKey}/verify/lookup.json"; var content = new FormUrlEncodedContent(new Dictionary { - ["receptor"] = phone, + ["receptor"] = NormalizePhone(phone), ["token"] = otp, ["template"] = template, }); @@ -182,6 +192,14 @@ public class KavenegarSmsService : ISmsService } } + // Strip leading 0 from Iranian mobile numbers (09xxxxxxxxx → 9xxxxxxxxx) + private static string NormalizePhone(string phone) + { + var p = phone.Trim(); + if (p.StartsWith("0") && p.Length == 11) return p[1..]; + return p; + } + private static string KavenegarHttpError(int code) => code switch { 400 => "Missing or invalid parameters", @@ -189,18 +207,26 @@ public class KavenegarSmsService : ISmsService 403 => "Invalid API key", 404 => "Method not found", 405 => "Wrong HTTP method", + 406 => "Recipient is on the blacklist or number is deactivated", 411 => "Invalid recipient number", 412 => "Invalid sender number", 413 => "Message empty or too long", 414 => "Too many recipients", + 415 => "Server error on Kavenegar side", + 416 => "Recipient is invalid, blacklisted, or deactivated", 417 => "Invalid scheduled date", 418 => "Insufficient credit", + 419 => "OTP token already used or expired", + 420 => "IP not allowed", + 421 => "Message could not be sent", 422 => "Invalid characters in message", - 424 => "OTP template not found", + 423 => "Kavenegar server unreachable", + 424 => "OTP template not found — check template name in Kavenegar panel", 426 => "IP is not whitelisted", 428 => "Voice call requires numeric token", + 431 => "SMS sending is disabled on this account", 432 => "Code parameter missing in OTP template", - _ => "Unknown error" + _ => $"Undocumented Kavenegar error {code}" }; private async Task<(string? ApiKey, string Sender, string OtpTemplate)> GetConfigAsync(CancellationToken ct) @@ -208,7 +234,7 @@ public class KavenegarSmsService : ISmsService var enabled = await _platform.GetAsync(DbKeyEnabled, ct); // If explicitly disabled in DB, short-circuit if (enabled is "false") - return (null, string.Empty, "meeziotp"); + return (null, string.Empty, "verify"); var apiKey = await _platform.GetAsync(DbKeyApiKey, ct); if (string.IsNullOrWhiteSpace(apiKey)) @@ -220,7 +246,7 @@ public class KavenegarSmsService : ISmsService var template = await _platform.GetAsync(DbKeyOtpTemplate, ct); if (string.IsNullOrWhiteSpace(template)) - template = _configuration["Kavenegar:OtpTemplate"] ?? "meeziotp"; + template = _configuration["Kavenegar:OtpTemplate"] ?? "verify"; return (apiKey, sender, template); }