Files
meezi/src/Meezi.Admin.API/Services/PlatformIntegrationService.cs
T

260 lines
13 KiB
C#
Raw Normal View History

2026-05-27 21:33:48 +03:30
using Meezi.Admin.API.Models;
using Meezi.Core.Platform;
using Meezi.Infrastructure.Services.Platform;
using Meezi.Core.Interfaces;
using Meezi.Core.Entities;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Meezi.Admin.API.Services;
public interface IPlatformIntegrationService
{
Task<PlatformIntegrationsDto> GetIntegrationsAsync(CancellationToken ct = default);
Task SaveIntegrationsAsync(UpdatePlatformIntegrationsRequest request, CancellationToken ct = default);
}
public class PlatformIntegrationService : IPlatformIntegrationService
{
public const string KeyActiveGateway = "payment.activeGateway";
2026-05-29 02:38:06 +03:30
public const string KeyKavenegarApi = "integrations.kavenegar.apiKey";
public const string KeyKavenegarOtpTemplate = "integrations.kavenegar.otpTemplate";
public const string KeyKavenegarEnabled = "integrations.kavenegar.enabled";
public const string KeyKavenegarSender = "integrations.kavenegar.senderNumber";
2026-05-27 21:33:48 +03:30
private static readonly (string Id, string NameFa, string Prefix)[] Gateways =
[
("zarinpal", "زرین‌پال", "payment.zarinpal"),
("tara", "تارا", "payment.tara"),
("snapppay", "اسنپ‌پی", "payment.snapppay"),
("nextpay", "نکست‌پی", "payment.nextpay"),
("vandar", "وندار", "payment.vandar")
];
private readonly AppDbContext _db;
private readonly IPlatformCatalogService _catalog;
private readonly IPlatformRuntimeConfig _runtime;
public PlatformIntegrationService(
AppDbContext db,
IPlatformCatalogService catalog,
IPlatformRuntimeConfig runtime)
{
_db = db;
_catalog = catalog;
_runtime = runtime;
}
public async Task<PlatformIntegrationsDto> GetIntegrationsAsync(CancellationToken ct = default)
{
var settings = await _db.PlatformSettings.AsNoTracking().ToListAsync(ct);
var map = settings.ToDictionary(s => s.Key, s => s.Value, StringComparer.OrdinalIgnoreCase);
var active = map.GetValueOrDefault(KeyActiveGateway) ?? "zarinpal";
var gateways = Gateways.Select(g => MapGateway(g.Id, g.NameFa, g.Prefix, active, map)).ToList();
var kavenegar = new KavenegarConfigDto(
map.GetValueOrDefault(KeyKavenegarEnabled) is "true",
MaskSecret(map.GetValueOrDefault(KeyKavenegarApi)),
2026-05-29 02:55:02 +03:30
map.GetValueOrDefault(KeyKavenegarOtpTemplate) ?? "meeziotp",
2026-05-29 02:38:06 +03:30
map.GetValueOrDefault(KeyKavenegarSender) ?? string.Empty,
2026-05-27 21:33:48 +03:30
HasSecret(map, KeyKavenegarApi));
var ai = new AiIntegrationsConfigDto(
new OpenAiIntegrationConfigDto(
map.GetValueOrDefault(PlatformIntegrationKeys.OpenAiEnabled) is not "false",
MaskSecret(map.GetValueOrDefault(PlatformIntegrationKeys.OpenAiApiKey)),
map.GetValueOrDefault(PlatformIntegrationKeys.OpenAiModel) ?? "gpt-4o-mini",
map.GetValueOrDefault(PlatformIntegrationKeys.OpenAiCoffeeAdvisorEnabled) is not "false",
HasSecret(map, PlatformIntegrationKeys.OpenAiApiKey)),
new MeshyIntegrationConfigDto(
map.GetValueOrDefault(PlatformIntegrationKeys.MeshyEnabled) is not "false",
MaskSecret(map.GetValueOrDefault(PlatformIntegrationKeys.MeshyApiKey)),
map.GetValueOrDefault(PlatformIntegrationKeys.MeshyMenu3dEnabled) is not "false",
HasSecret(map, PlatformIntegrationKeys.MeshyApiKey)));
return new PlatformIntegrationsDto(active, gateways, kavenegar, ai);
}
public async Task SaveIntegrationsAsync(UpdatePlatformIntegrationsRequest request, CancellationToken ct = default)
{
var active = request.ActivePaymentGateway.Trim().ToLowerInvariant();
if (!Gateways.Any(g => g.Id == active))
active = "zarinpal";
await UpsertAsync(KeyActiveGateway, active, "payment", "درگاه پیش‌فرض اشتراک", ct);
foreach (var gw in request.PaymentGateways)
{
var meta = Gateways.FirstOrDefault(g => g.Id == gw.Id);
if (string.IsNullOrEmpty(meta.Id)) continue;
await UpsertAsync($"{meta.Prefix}.enabled", gw.IsEnabled ? "true" : "false", "payment", $"فعال {meta.NameFa}", ct);
await UpsertAsync($"{meta.Prefix}.sandbox", gw.Sandbox ? "true" : "false", "payment", $"حالت تست {meta.NameFa}", ct);
if (gw.Id == "zarinpal")
{
if (!string.IsNullOrWhiteSpace(gw.MerchantId))
await UpsertAsync($"{meta.Prefix}.merchantId", gw.MerchantId.Trim(), "payment", "مرچنت زرین‌پال", ct);
}
else if (gw.Id is "nextpay" or "vandar")
{
if (!string.IsNullOrWhiteSpace(gw.ApiKey) && !IsMaskedPlaceholder(gw.ApiKey))
await UpsertAsync($"{meta.Prefix}.apiKey", gw.ApiKey.Trim(), "payment", $"توکن {meta.NameFa}", ct);
}
if (gw.Credentials is not null)
await SaveCredentialsAsync(meta.Prefix, gw.Id, gw.Credentials, ct);
}
await UpsertAsync(KeyKavenegarEnabled, request.Kavenegar.IsEnabled ? "true" : "false", "integrations", "فعال کاوه‌نگار", ct);
await UpsertAsync(KeyKavenegarOtpTemplate, request.Kavenegar.OtpTemplate.Trim(), "integrations", "قالب OTP", ct);
if (!string.IsNullOrWhiteSpace(request.Kavenegar.ApiKey) && !IsMaskedPlaceholder(request.Kavenegar.ApiKey))
await UpsertAsync(KeyKavenegarApi, request.Kavenegar.ApiKey.Trim(), "integrations", "API Key کاوه‌نگار", ct);
2026-05-29 02:38:06 +03:30
if (!string.IsNullOrWhiteSpace(request.Kavenegar.SenderNumber))
await UpsertAsync(KeyKavenegarSender, request.Kavenegar.SenderNumber.Trim(), "integrations", "شماره فرستنده کاوه‌نگار", ct);
2026-05-27 21:33:48 +03:30
await UpsertAsync(PlatformIntegrationKeys.OpenAiEnabled, request.Ai.OpenAi.IsEnabled ? "true" : "false", "integrations", "فعال OpenAI", ct);
await UpsertAsync(PlatformIntegrationKeys.OpenAiModel, string.IsNullOrWhiteSpace(request.Ai.OpenAi.Model) ? "gpt-4o-mini" : request.Ai.OpenAi.Model.Trim(), "integrations", "مدل OpenAI", ct);
await UpsertAsync(PlatformIntegrationKeys.OpenAiCoffeeAdvisorEnabled, request.Ai.OpenAi.CoffeeAdvisorEnabled ? "true" : "false", "integrations", "مشاور قهوه OpenAI", ct);
if (!string.IsNullOrWhiteSpace(request.Ai.OpenAi.ApiKey) && !IsMaskedPlaceholder(request.Ai.OpenAi.ApiKey))
await UpsertAsync(PlatformIntegrationKeys.OpenAiApiKey, request.Ai.OpenAi.ApiKey.Trim(), "integrations", "API Key OpenAI", ct);
await UpsertAsync(PlatformIntegrationKeys.MeshyEnabled, request.Ai.Meshy.IsEnabled ? "true" : "false", "integrations", "فعال Meshy", ct);
await UpsertAsync(PlatformIntegrationKeys.MeshyMenu3dEnabled, request.Ai.Meshy.Menu3dEnabled ? "true" : "false", "integrations", "ساخت ۳D منو با Meshy", ct);
if (!string.IsNullOrWhiteSpace(request.Ai.Meshy.ApiKey) && !IsMaskedPlaceholder(request.Ai.Meshy.ApiKey))
await UpsertAsync(PlatformIntegrationKeys.MeshyApiKey, request.Ai.Meshy.ApiKey.Trim(), "integrations", "API Key Meshy", ct);
await _db.SaveChangesAsync(ct);
_catalog.InvalidateCache();
_runtime.InvalidateCache();
}
private async Task SaveCredentialsAsync(
string prefix,
string gatewayId,
UpdatePaymentGatewayCredentialsRequest creds,
CancellationToken ct)
{
if (!string.IsNullOrWhiteSpace(creds.BaseUrl))
await UpsertAsync($"{prefix}.baseUrl", creds.BaseUrl.Trim(), "payment", "آدرس API", ct);
if (gatewayId == "tara")
{
if (!string.IsNullOrWhiteSpace(creds.Username))
await UpsertAsync($"{prefix}.username", creds.Username.Trim(), "payment", "نام کاربری تارا", ct);
if (!string.IsNullOrWhiteSpace(creds.Password) && !IsMaskedPlaceholder(creds.Password))
await UpsertAsync($"{prefix}.password", creds.Password.Trim(), "payment", "رمز تارا", ct);
if (!string.IsNullOrWhiteSpace(creds.BranchCode))
await UpsertAsync($"{prefix}.branchCode", creds.BranchCode.Trim(), "payment", "کد شعبه تارا", ct);
if (!string.IsNullOrWhiteSpace(creds.TerminalCode))
await UpsertAsync($"{prefix}.terminalCode", creds.TerminalCode.Trim(), "payment", "ترمینال تارا", ct);
}
else if (gatewayId == "snapppay")
{
if (!string.IsNullOrWhiteSpace(creds.ClientId))
await UpsertAsync($"{prefix}.clientId", creds.ClientId.Trim(), "payment", "Client ID اسنپ‌پی", ct);
if (!string.IsNullOrWhiteSpace(creds.ClientSecret) && !IsMaskedPlaceholder(creds.ClientSecret))
await UpsertAsync($"{prefix}.clientSecret", creds.ClientSecret.Trim(), "payment", "Client Secret اسنپ‌پی", ct);
if (!string.IsNullOrWhiteSpace(creds.Username))
await UpsertAsync($"{prefix}.username", creds.Username.Trim(), "payment", "نام کاربری اسنپ‌پی", ct);
if (!string.IsNullOrWhiteSpace(creds.Password) && !IsMaskedPlaceholder(creds.Password))
await UpsertAsync($"{prefix}.password", creds.Password.Trim(), "payment", "رمز اسنپ‌پی", ct);
}
}
private async Task UpsertAsync(string key, string value, string category, string descFa, CancellationToken ct)
{
var row = await _db.PlatformSettings.FirstOrDefaultAsync(s => s.Key == key, ct);
if (row is null)
{
_db.PlatformSettings.Add(new PlatformSetting
{
Key = key,
Value = value,
Category = category,
DescriptionFa = descFa
});
}
else
{
row.Value = value;
row.UpdatedAt = DateTime.UtcNow;
}
}
private static PaymentGatewayConfigDto MapGateway(
string id,
string nameFa,
string prefix,
string activeGateway,
Dictionary<string, string> map)
{
var enabled = map.GetValueOrDefault($"{prefix}.enabled") is "true";
var sandbox = map.GetValueOrDefault($"{prefix}.sandbox") is not "false";
string? merchantId = null;
string? apiKey = null;
var hasSecret = false;
GatewayCredentialsDto? credentials = null;
if (id == "zarinpal")
{
merchantId = map.GetValueOrDefault($"{prefix}.merchantId");
hasSecret = HasSecret(map, $"{prefix}.merchantId");
}
else if (id is "nextpay" or "vandar")
{
apiKey = MaskSecret(map.GetValueOrDefault($"{prefix}.apiKey"));
hasSecret = HasSecret(map, $"{prefix}.apiKey");
}
else if (id == "tara")
{
credentials = new GatewayCredentialsDto(
map.GetValueOrDefault($"{prefix}.username"),
MaskSecret(map.GetValueOrDefault($"{prefix}.password")),
map.GetValueOrDefault($"{prefix}.branchCode"),
map.GetValueOrDefault($"{prefix}.terminalCode"),
null,
null,
map.GetValueOrDefault($"{prefix}.baseUrl"),
HasSecret(map, $"{prefix}.password"),
false);
hasSecret = credentials.HasStoredPassword;
}
else if (id == "snapppay")
{
credentials = new GatewayCredentialsDto(
map.GetValueOrDefault($"{prefix}.username"),
MaskSecret(map.GetValueOrDefault($"{prefix}.password")),
null,
null,
map.GetValueOrDefault($"{prefix}.clientId"),
MaskSecret(map.GetValueOrDefault($"{prefix}.clientSecret")),
map.GetValueOrDefault($"{prefix}.baseUrl"),
HasSecret(map, $"{prefix}.password"),
HasSecret(map, $"{prefix}.clientSecret"));
hasSecret = credentials.HasStoredPassword || credentials.HasStoredClientSecret;
}
return new PaymentGatewayConfigDto(
id,
nameFa,
enabled,
activeGateway == id,
merchantId,
apiKey,
sandbox,
hasSecret,
credentials);
}
private static bool HasSecret(Dictionary<string, string> map, string key) =>
!string.IsNullOrWhiteSpace(map.GetValueOrDefault(key));
private static string? MaskSecret(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : "••••••••";
private static bool IsMaskedPlaceholder(string? value) =>
string.IsNullOrWhiteSpace(value) || value.Contains("••••", StringComparison.Ordinal);
}