feat: implement Kavenegar SMS service and refactor system configurations to use static constants
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m38s

This commit is contained in:
masoodafar-web
2025-12-26 09:04:27 +03:30
parent 9d2b5ad2d4
commit 02e2f8111f
51 changed files with 4064 additions and 1388 deletions
@@ -34,6 +34,7 @@ public static class ConfigureServices
services.AddScoped<INetworkPlacementService, NetworkPlacementService>();
services.AddScoped<IAlertService, AlertService>();
services.AddScoped<IUserNotificationService, UserNotificationService>();
services.AddScoped<IKavenegarService, KavenegarService>();
// Daya Loan API Service - قابل تغییر بین Mock و Real
var useMockDayaApi = configuration.GetValue<bool>("DayaApi:UseMock", false);
@@ -84,9 +84,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
// ============= Network Club System DbSets =============
// Configuration
public DbSet<SystemConfiguration> SystemConfigurations => Set<SystemConfiguration>();
public DbSet<SystemConfigurationHistory> SystemConfigurationHistories => Set<SystemConfigurationHistory>();
// App Version
public DbSet<AppVersion> AppVersions => Set<AppVersion>();
// Club Management
@@ -1,12 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CMSMicroservice.Domain.Entities.Configuration;
using CMSMicroservice.Domain.Enums;
using System.Collections.Generic;
namespace CMSMicroservice.Infrastructure.Persistence;
public class ApplicationDbContextInitialiser
public class ApplicationDbContextInitialiser
{
private readonly ApplicationDbContext _context;
private readonly ILogger<ApplicationDbContextInitialiser> _logger;
@@ -32,6 +29,7 @@ public class ApplicationDbContextInitialiser
throw;
}
}
public async Task SeedAsync()
{
try
@@ -44,113 +42,12 @@ public class ApplicationDbContextInitialiser
throw;
}
}
public async Task TrySeedAsync()
public Task TrySeedAsync()
{
// Seed / upsert default System Configurations for Network-Club-Commission System
var desiredConfigurations = new List<SystemConfiguration>
{
// Network Configuration
new SystemConfiguration
{
Key = "Network.MaxNetworkDepth",
Value = "15",
Description = "حداکثر عمق شبکه باینری",
Scope = ConfigurationScope.Network,
IsActive = true
},
new SystemConfiguration
{
Key = "Network.MaxChildrenPerLeg",
Value = "1",
Description = "حداکثر تعداد فرزند مستقیم در هر پا",
Scope = ConfigurationScope.Network,
IsActive = true
},
// Commission Configuration
new SystemConfiguration
{
Key = "Commission.MaxWeeklyBalancesPerLeg",
Value = "300",
Description = "سقف تعادل هفتگی برای هر دست (چپ یا راست) - حداکثر کل = 600",
Scope = ConfigurationScope.Commission,
IsActive = true
},
new SystemConfiguration
{
Key = "Commission.MaxNetworkLevel",
Value = "15",
Description = "حداکثر عمق شبکه برای محاسبه کمیسیون (تعداد لول زیرمجموعه)",
Scope = ConfigurationScope.Commission,
IsActive = true
},
new SystemConfiguration
{
Key = "Commission.MinWithdrawalAmount",
Value = "1000000",
Description = "حداقل مبلغ برداشت (ریال)",
Scope = ConfigurationScope.Commission,
IsActive = true
},
new SystemConfiguration
{
Key = "Commission.DefaultInitialContribution",
Value = "25000000",
Description = "مبلغ پیش‌فرض مشارکت/هزینه فعال‌سازی",
Scope = ConfigurationScope.Commission,
IsActive = true
},
new SystemConfiguration
{
Key = "Commission.WeeklyPoolContributionPercent",
Value = "20",
Description = "درصد مشارکت در استخر هفتگی از کل فعال‌سازی‌های جدید شبکه (20%)",
Scope = ConfigurationScope.Commission,
IsActive = true
},
// Club Configuration
new SystemConfiguration
{
Key = "Club.ActivationFee",
Value = "25000000",
Description = "هزینه فعال‌سازی عضویت باشگاه (ریال)",
Scope = ConfigurationScope.Club,
IsActive = true
},
new SystemConfiguration
{
Key = "Club.MembershipGiftValue",
Value = "25200000",
Description = "مبلغ هدیه حق عضویت باشگاه (ریال) - این مبلغ از کیف پول کم نمی‌شود",
Scope = ConfigurationScope.Club,
IsActive = true
},
// System Configuration
new SystemConfiguration
{
Key = "System.EnableAuditLog",
Value = "true",
Description = "فعال‌سازی لاگ تغییرات",
Scope = ConfigurationScope.System,
IsActive = true
}
};
var existingKeys = _context.SystemConfigurations
.Select(c => c.Key)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var newConfigs = desiredConfigurations
.Where(c => !existingKeys.Contains(c.Key))
.ToList();
if (newConfigs.Any())
{
await _context.SystemConfigurations.AddRangeAsync(newConfigs);
await _context.SaveChangesAsync();
_logger.LogInformation("Seeded {Count} default system configurations", newConfigs.Count);
}
// SystemConfigurations دیگه در دیتابیس نیست
// مقادیر کانفیگ حالا در SystemConstants.cs به صورت const تعریف شدن
_logger.LogInformation("Database seeding completed. System configurations are now defined as compile-time constants in SystemConstants.cs");
return Task.CompletedTask;
}
}
@@ -1,35 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
/// <summary>
/// تنظیمات پویای سیستم
/// </summary>
public class SystemConfigurationConfiguration : IEntityTypeConfiguration<SystemConfiguration>
{
public void Configure(EntityTypeBuilder<SystemConfiguration> builder)
{
builder.HasQueryFilter(p => !p.IsDeleted);
builder.Ignore(entity => entity.DomainEvents);
builder.HasKey(entity => entity.Id);
builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.Scope).IsRequired();
builder.Property(entity => entity.Key).IsRequired().HasMaxLength(200);
builder.Property(entity => entity.Value).IsRequired().HasMaxLength(1000);
builder.Property(entity => entity.DataType).IsRequired(false).HasMaxLength(50);
builder.Property(entity => entity.Description).IsRequired(false).HasMaxLength(500);
builder.Property(entity => entity.IsActive).IsRequired();
// Composite Index برای جستجوی سریع
builder.HasIndex(e => new { e.Scope, e.Key })
.IsUnique()
.HasDatabaseName("IX_SystemConfiguration_Scope_Key");
// Index برای IsActive
builder.HasIndex(e => e.IsActive)
.HasDatabaseName("IX_SystemConfiguration_IsActive");
}
}
@@ -1,41 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
/// <summary>
/// تاریخچه تغییرات تنظیمات سیستم
/// </summary>
public class SystemConfigurationHistoryConfiguration : IEntityTypeConfiguration<SystemConfigurationHistory>
{
public void Configure(EntityTypeBuilder<SystemConfigurationHistory> builder)
{
builder.HasQueryFilter(p => !p.IsDeleted);
builder.Ignore(entity => entity.DomainEvents);
builder.HasKey(entity => entity.Id);
builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.ConfigurationId).IsRequired();
builder.Property(entity => entity.Scope).IsRequired();
builder.Property(entity => entity.Key).IsRequired().HasMaxLength(200);
builder.Property(entity => entity.OldValue).IsRequired().HasMaxLength(1000);
builder.Property(entity => entity.NewValue).IsRequired().HasMaxLength(1000);
builder.Property(entity => entity.Reason).IsRequired(false).HasMaxLength(500);
builder.Property(entity => entity.PerformedBy).IsRequired(false).HasMaxLength(100);
// رابطه با SystemConfiguration
builder.HasOne(entity => entity.Configuration)
.WithMany(sc => sc.SystemConfigurationHistories)
.HasForeignKey(entity => entity.ConfigurationId)
.OnDelete(DeleteBehavior.Restrict);
// Index برای ConfigurationId و Created
builder.HasIndex(e => new { e.ConfigurationId, e.Created })
.HasDatabaseName("IX_SystemConfigurationHistory_ConfigId_Created");
// Index برای Scope و Key
builder.HasIndex(e => new { e.Scope, e.Key })
.HasDatabaseName("IX_SystemConfigurationHistory_Scope_Key");
}
}
@@ -0,0 +1,108 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class RemoveSystemConfigurationsTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SystemConfigurationHistories",
schema: "CMS");
migrationBuilder.DropTable(
name: "SystemConfigurations",
schema: "CMS");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SystemConfigurations",
schema: "CMS",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
DataType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
Key = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
Scope = table.Column<int>(type: "int", nullable: false),
Value = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SystemConfigurations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SystemConfigurationHistories",
schema: "CMS",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ConfigurationId = table.Column<long>(type: "bigint", nullable: false),
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
Key = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
NewValue = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
OldValue = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
PerformedBy = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
Reason = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
Scope = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SystemConfigurationHistories", x => x.Id);
table.ForeignKey(
name: "FK_SystemConfigurationHistories_SystemConfigurations_ConfigurationId",
column: x => x.ConfigurationId,
principalSchema: "CMS",
principalTable: "SystemConfigurations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_SystemConfigurationHistory_ConfigId_Created",
schema: "CMS",
table: "SystemConfigurationHistories",
columns: new[] { "ConfigurationId", "Created" });
migrationBuilder.CreateIndex(
name: "IX_SystemConfigurationHistory_Scope_Key",
schema: "CMS",
table: "SystemConfigurationHistories",
columns: new[] { "Scope", "Key" });
migrationBuilder.CreateIndex(
name: "IX_SystemConfiguration_IsActive",
schema: "CMS",
table: "SystemConfigurations",
column: "IsActive");
migrationBuilder.CreateIndex(
name: "IX_SystemConfiguration_Scope_Key",
schema: "CMS",
table: "SystemConfigurations",
columns: new[] { "Scope", "Key" },
unique: true);
}
}
}
@@ -503,65 +503,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.ToTable("AppVersions", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.SystemConfiguration", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("DataType")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<int>("Scope")
.HasColumnType("int");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.HasKey("Id");
b.HasIndex("IsActive")
.HasDatabaseName("IX_SystemConfiguration_IsActive");
b.HasIndex("Scope", "Key")
.IsUnique()
.HasDatabaseName("IX_SystemConfiguration_Scope_Key");
b.ToTable("SystemConfigurations", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b =>
{
b.Property<long>("Id")
@@ -1504,69 +1445,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.ToTable("NetworkMembershipHistories", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.SystemConfigurationHistory", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<long>("ConfigurationId")
.HasColumnType("bigint");
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("NewValue")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("OldValue")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("PerformedBy")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Reason")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int>("Scope")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ConfigurationId", "Created")
.HasDatabaseName("IX_SystemConfigurationHistory_ConfigId_Created");
b.HasIndex("Scope", "Key")
.HasDatabaseName("IX_SystemConfigurationHistory_Scope_Key");
b.ToTable("SystemConfigurationHistories", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b =>
{
b.Property<long>("Id")
@@ -3238,17 +3116,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Navigation("WeekDefinition");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.SystemConfigurationHistory", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Configuration.SystemConfiguration", "Configuration")
.WithMany("SystemConfigurationHistories")
.HasForeignKey("ConfigurationId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Configuration");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
@@ -3553,11 +3420,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Navigation("UserCommissionPayouts");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.SystemConfiguration", b =>
{
b.Navigation("SystemConfigurationHistories");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b =>
{
b.Navigation("UserContracts");
@@ -65,19 +65,11 @@ BEGIN
AND IsActive = 1;
-- =============================================
-- 4. خواندن Configuration ها
-- 4. مقادیر ثابت (Hardcoded - از SystemConstants)
-- =============================================
SELECT @MaxBalancesPerLeg = CAST(Value AS INT)
FROM CMS.SystemConfigurations
WHERE [Key] = 'Commission.MaxWeeklyBalancesPerLeg' AND IsActive = 1;
SELECT @MaxNetworkLevel = CAST(Value AS INT)
FROM CMS.SystemConfigurations
WHERE [Key] = 'Commission.MaxNetworkLevel' AND IsActive = 1;
-- مقادیر پیش‌فرض
SET @MaxBalancesPerLeg = ISNULL(@MaxBalancesPerLeg, 300);
SET @MaxNetworkLevel = ISNULL(@MaxNetworkLevel, 15);
-- این مقادیر ثابت هستند و تغییر نمی‌کنند
SET @MaxBalancesPerLeg = 300; -- سقف تعادل هر پا
SET @MaxNetworkLevel = 15; -- حداکثر عمق شبکه
-- =============================================
-- 5. ایجاد جدول موقت برای نتایج
@@ -1,4 +1,5 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -13,13 +14,6 @@ public class CommissionCalculationStrategyFactory : ICommissionCalculationStrate
private readonly IWeekDefinitionRepository _weekRepository;
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// کلید Config برای انتخاب استراتژی
/// مقدار: "ORM" یا "SP"
/// پیش‌فرض: "ORM"
/// </summary>
private const string ConfigKey = "Commission.CalculationStrategy";
public CommissionCalculationStrategyFactory(
IApplicationDbContext context,
IWeekDefinitionRepository weekRepository,
@@ -31,13 +25,10 @@ public class CommissionCalculationStrategyFactory : ICommissionCalculationStrate
}
/// <inheritdoc />
public async Task<ICommissionCalculationStrategy> CreateStrategyAsync(CancellationToken cancellationToken = default)
public Task<ICommissionCalculationStrategy> CreateStrategyAsync(CancellationToken cancellationToken = default)
{
// خواندن Config از دیتابیس
var config = await _context.SystemConfigurations
.FirstOrDefaultAsync(x => x.Key == ConfigKey && x.IsActive, cancellationToken);
var strategyValue = config?.Value?.ToUpperInvariant() ?? "ORM";
// خواندن Config از SystemConstants (استاتیک)
var strategyValue = SystemConstants.CommissionCalculationStrategy.ToUpperInvariant();
var strategyType = strategyValue switch
{
@@ -45,7 +36,7 @@ public class CommissionCalculationStrategyFactory : ICommissionCalculationStrate
_ => CommissionCalculationStrategyType.Orm
};
return CreateStrategy(strategyType);
return Task.FromResult(CreateStrategy(strategyType));
}
/// <inheritdoc />
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Entities.Club;
using CMSMicroservice.Domain.Entities.Commission;
using CMSMicroservice.Domain.Entities.Network;
@@ -87,15 +88,9 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
var balancesList = new List<NetworkWeeklyBalance>();
var calculatedAt = DateTime.Now;
// خواندن یکباره Configuration ها
var configs = await _context.SystemConfigurations
.Where(x => x.IsActive && (
x.Key == "Commission.MaxWeeklyBalancesPerLeg" ||
x.Key == "Commission.MaxNetworkLevel"))
.ToDictionaryAsync(x => x.Key, x => x.Value, cancellationToken);
var maxBalancesPerLeg = int.Parse(configs.GetValueOrDefault("Commission.MaxWeeklyBalancesPerLeg", "300"));
var maxNetworkLevel = int.Parse(configs.GetValueOrDefault("Commission.MaxNetworkLevel", "15"));
// استفاده از SystemConstants به جای دیتابیس
var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg;
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
foreach (var user in usersInNetwork.OrderBy(o => o.Id))
{
@@ -0,0 +1,109 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Infrastructure.Configuration;
using Kavenegar;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace CMSMicroservice.Infrastructure.Services;
/// <summary>
/// پیاده‌سازی سرویس ارسال SMS با کاوه‌نگار
/// </summary>
public class KavenegarService : IKavenegarService
{
private readonly KavenegarApi? _kavenegarApi;
private readonly SmsSettings _smsSettings;
private readonly ILogger<KavenegarService> _logger;
public KavenegarService(
IOptions<SmsSettings> smsSettings,
ILogger<KavenegarService> logger)
{
_smsSettings = smsSettings.Value;
_logger = logger;
// Initialize Kavenegar API
if (_smsSettings.Enabled && !string.IsNullOrEmpty(_smsSettings.KavenegarApiKey))
{
try
{
_kavenegarApi = new KavenegarApi(_smsSettings.KavenegarApiKey);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ Failed to initialize Kavenegar API");
}
}
}
/// <inheritdoc />
public async Task SendAsync(string mobile, string message)
{
if (!_smsSettings.Enabled)
{
_logger.LogInformation("SMS is disabled. Skipping send to {Mobile}", mobile);
return;
}
if (_kavenegarApi == null)
{
_logger.LogWarning("⚠️ Kavenegar API not initialized, cannot send SMS");
return;
}
try
{
// Kavenegar Send is synchronous
await Task.Run(() =>
{
var result = _kavenegarApi.Send(
sender: _smsSettings.Sender,
receptor: mobile,
message: message);
_logger.LogInformation("📱 SMS sent successfully to {Mobile}, MessageId: {MessageId}", mobile, result.Messageid);
});
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ Kavenegar error sending SMS to {Mobile}: {Message}", mobile, ex.Message);
throw;
}
}
/// <inheritdoc />
public async Task VerifyLookupAsync(string mobile, string token, string template = "Afrino")
{
if (!_smsSettings.Enabled)
{
_logger.LogInformation("SMS is disabled. Skipping VerifyLookup to {Mobile}", mobile);
return;
}
if (_kavenegarApi == null)
{
_logger.LogWarning("⚠️ Kavenegar API not initialized, cannot send VerifyLookup");
return;
}
try
{
// Kavenegar VerifyLookup is synchronous
await Task.Run(() =>
{
var result = _kavenegarApi.VerifyLookup(
receptor: mobile,
token: token,
template: template);
_logger.LogInformation("📱 VerifyLookup SMS sent successfully to {Mobile} with template {Template}, MessageId: {MessageId}",
mobile, template, result.Messageid);
});
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ Kavenegar error sending VerifyLookup to {Mobile}: {Message}", mobile, ex.Message);
throw;
}
}
}