feat: Implement real Daya API integration with configurable mock/real service

This commit is contained in:
masoodafar-web
2025-12-06 21:02:51 +03:30
parent e46f54ca5b
commit a0da7eb8e0
6 changed files with 274 additions and 20 deletions

View File

@@ -6,17 +6,28 @@ namespace CMSMicroservice.Domain.Enums;
public enum DayaLoanStatus public enum DayaLoanStatus
{ {
/// <summary> /// <summary>
/// در انتظار دریافت وام (خرید انجام شده، قرارداد امضا شده، درخواست وام ثبت شده) /// هنوز درخواست نشده
/// </summary> /// </summary>
PendingReceive = 0, NotRequested = 0,
/// <summary> /// <summary>
/// وام دریافت شده (در آینده اضافه می‌شود) /// در انتظار دریافت وام (قرارداد امضا شده، در انتظار تسویه)
/// وضعیت: "فعال شده (در انتظار تسویه)"
/// </summary> /// </summary>
Received = 1, PendingReceive = 1,
/// <summary> /// <summary>
/// رد شده (در آینده اضافه می‌شود) /// وام دریافت شده و شارژ شده
/// </summary> /// </summary>
Rejected = 2, Received = 2,
/// <summary>
/// رد شده
/// </summary>
Rejected = 3,
/// <summary>
/// در حال بررسی
/// </summary>
UnderReview = 4,
} }

View File

@@ -32,7 +32,39 @@ public static class ConfigureServices
services.AddScoped<INetworkPlacementService, NetworkPlacementService>(); services.AddScoped<INetworkPlacementService, NetworkPlacementService>();
services.AddScoped<IAlertService, AlertService>(); services.AddScoped<IAlertService, AlertService>();
services.AddScoped<IUserNotificationService, UserNotificationService>(); services.AddScoped<IUserNotificationService, UserNotificationService>();
services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>(); // Mock - جایگزین با Real برای Production
// Daya Loan API Service - قابل تغییر بین Mock و Real
var useMockDayaApi = configuration.GetValue<bool>("DayaApi:UseMock", false);
if (useMockDayaApi)
{
// Mock برای Development/Testing
services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>();
}
else
{
// Real Implementation با HttpClient
services.AddHttpClient<IDayaLoanApiService, DayaLoanApiService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.ConfigureHttpClient((sp, client) =>
{
var config = sp.GetRequiredService<IConfiguration>();
// Base Address
var baseAddress = config["DayaApi:BaseAddress"] ?? "https://testdaya.tadbirandishan.com";
client.BaseAddress = new Uri(baseAddress);
// Merchant Permission Key
var permissionKey = config["DayaApi:MerchantPermissionKey"];
if (!string.IsNullOrEmpty(permissionKey))
{
client.DefaultRequestHeaders.Add("merchant-permission-key", permissionKey);
}
// Timeout
client.Timeout = TimeSpan.FromSeconds(30);
});
}
// Payment Gateway Service - فقط Daya (درگاه اینترنتی از Gateway میاد نه CMS) // Payment Gateway Service - فقط Daya (درگاه اینترنتی از Gateway میاد نه CMS)
var useRealPaymentGateway = configuration.GetValue<bool>("UseRealPaymentGateway", false); var useRealPaymentGateway = configuration.GetValue<bool>("UseRealPaymentGateway", false);

View File

@@ -1,11 +1,15 @@
using CMSMicroservice.Application.DayaLoanCQ.Services; using CMSMicroservice.Application.DayaLoanCQ.Services;
using CMSMicroservice.Domain.Enums; using CMSMicroservice.Domain.Enums;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Text.Json.Serialization;
using System.Linq;
namespace CMSMicroservice.Infrastructure.Services; namespace CMSMicroservice.Infrastructure.Services;
@@ -74,31 +78,232 @@ public class MockDayaLoanApiService : IDayaLoanApiService
/// <summary> /// <summary>
/// Real Implementation برای API واقعی دایا /// Real Implementation برای API واقعی دایا
/// TODO: این کلاس باید پیاده‌سازی شود زمانی که API دایا آماده شد /// مبتنی بر مستند TA-DAYA-S10-G-MerchantServices
/// </summary> /// </summary>
public class DayaLoanApiService : IDayaLoanApiService public class DayaLoanApiService : IDayaLoanApiService
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILogger<DayaLoanApiService> _logger; private readonly ILogger<DayaLoanApiService> _logger;
private readonly IConfiguration _configuration;
public DayaLoanApiService(HttpClient httpClient, ILogger<DayaLoanApiService> logger) public DayaLoanApiService(
HttpClient httpClient,
ILogger<DayaLoanApiService> logger,
IConfiguration configuration)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_configuration = configuration;
// تنظیم Base Address از Configuration
var baseAddress = _configuration["DayaApi:BaseAddress"] ?? "https://testdaya.tadbirandishan.com";
_httpClient.BaseAddress = new Uri(baseAddress);
// تنظیم merchant-permission-key از Configuration
var permissionKey = _configuration["DayaApi:MerchantPermissionKey"];
if (!string.IsNullOrEmpty(permissionKey))
{
_httpClient.DefaultRequestHeaders.Add("merchant-permission-key", permissionKey);
}
else
{
_logger.LogWarning("⚠️ DayaApi:MerchantPermissionKey is not configured in appsettings!");
}
} }
public async Task<List<DayaLoanStatusResult>> CheckLoanStatusAsync( public async Task<List<DayaLoanStatusResult>> CheckLoanStatusAsync(
List<string> nationalCodes, List<string> nationalCodes,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
// TODO: پیاده‌سازی واقعی API دایا try
// مثال: {
// var request = new DayaApiRequest { NationalCodes = nationalCodes }; _logger.LogInformation("Calling Daya API for {Count} national codes", nationalCodes.Count);
// var response = await _httpClient.PostAsJsonAsync("/api/loan/check", request, cancellationToken);
// response.EnsureSuccessStatusCode();
// var result = await response.Content.ReadFromJsonAsync<DayaApiResponse>(cancellationToken);
// return MapToResults(result);
throw new NotImplementedException("Real Daya API is not implemented yet. Use MockDayaLoanApiService for testing."); // ساخت Request Body
var requestBody = new DayaContractsRequest
{
NationalCodes = nationalCodes
};
// فراخوانی API
var response = await _httpClient.PostAsJsonAsync(
"/api/merchant/contracts",
requestBody,
cancellationToken);
// خواندن پاسخ
var apiResponse = await response.Content.ReadFromJsonAsync<DayaContractsResponse>(cancellationToken);
// بررسی موفقیت
if (apiResponse == null)
{
_logger.LogError("Daya API returned null response");
return CreateEmptyResults(nationalCodes);
}
if (!apiResponse.Succeed)
{
_logger.LogWarning("Daya API call failed. Code: {Code}, Message: {Message}",
apiResponse.Code, apiResponse.Message);
return CreateEmptyResults(nationalCodes);
}
if (apiResponse.Data == null || !apiResponse.Data.Any())
{
_logger.LogInformation("Daya API returned no contract data");
return CreateEmptyResults(nationalCodes);
}
// تبدیل نتایج API به مدل داخلی
var results = MapApiResponseToResults(apiResponse.Data, nationalCodes);
_logger.LogInformation("Daya API returned {Count} contracts for {RequestCount} national codes",
results.Count(r => !string.IsNullOrEmpty(r.ContractNumber)),
nationalCodes.Count);
return results;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP error calling Daya API. Status: {StatusCode}", ex.StatusCode);
return CreateEmptyResults(nationalCodes);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error calling Daya API");
return CreateEmptyResults(nationalCodes);
}
}
/// <summary>
/// تبدیل پاسخ API دایا به مدل داخلی
/// </summary>
private List<DayaLoanStatusResult> MapApiResponseToResults(
List<DayaContractData> apiData,
List<string> requestedNationalCodes)
{
var results = new List<DayaLoanStatusResult>();
// برای هر کد ملی درخواستی
foreach (var nationalCode in requestedNationalCodes)
{
// پیدا کردن آخرین قرارداد این کد ملی (براساس تاریخ)
var latestContract = apiData
.Where(d => d.NationalCode == nationalCode)
.OrderByDescending(d => d.DateTime)
.FirstOrDefault();
if (latestContract != null)
{
// تعیین وضعیت براساس StatusDescription
var status = MapStatusDescription(latestContract.StatusDescription);
results.Add(new DayaLoanStatusResult
{
NationalCode = nationalCode,
Status = status,
ContractNumber = latestContract.ApplicationNo
});
_logger.LogDebug("Daya contract found for {NationalCode}: {ContractNo}, Status: {Status}",
nationalCode, latestContract.ApplicationNo, latestContract.StatusDescription);
}
else
{
// هیچ قراردادی برای این کد ملی پیدا نشد
results.Add(new DayaLoanStatusResult
{
NationalCode = nationalCode,
Status = DayaLoanStatus.NotRequested,
ContractNumber = null
});
}
}
return results;
}
/// <summary>
/// تبدیل StatusDescription دایا به Enum داخلی
/// </summary>
private DayaLoanStatus MapStatusDescription(string statusDescription)
{
if (string.IsNullOrWhiteSpace(statusDescription))
return DayaLoanStatus.NotRequested;
// براساس مستند دایا: "فعال شده (در انتظار تسویه)" = وام تایید شده
if (statusDescription.Contains("فعال شده") || statusDescription.Contains("در انتظار تسویه"))
return DayaLoanStatus.PendingReceive;
if (statusDescription.Contains("رد شده") || statusDescription.Contains("رد"))
return DayaLoanStatus.Rejected;
if (statusDescription.Contains("بررسی") || statusDescription.Contains("در حال"))
return DayaLoanStatus.UnderReview;
// پیش‌فرض
return DayaLoanStatus.NotRequested;
}
/// <summary>
/// ساخت نتایج خالی در صورت خطا
/// </summary>
private List<DayaLoanStatusResult> CreateEmptyResults(List<string> nationalCodes)
{
return nationalCodes.Select(nc => new DayaLoanStatusResult
{
NationalCode = nc,
Status = DayaLoanStatus.NotRequested,
ContractNumber = null
}).ToList();
} }
} }
#region Daya API Models
/// <summary>
/// Request body برای /api/merchant/contracts
/// </summary>
internal class DayaContractsRequest
{
[JsonPropertyName("NationalCodes")]
public List<string> NationalCodes { get; set; } = new();
}
/// <summary>
/// Response از /api/merchant/contracts
/// </summary>
internal class DayaContractsResponse
{
[JsonPropertyName("succeed")]
public bool Succeed { get; set; }
[JsonPropertyName("code")]
public int Code { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
[JsonPropertyName("data")]
public List<DayaContractData>? Data { get; set; }
}
/// <summary>
/// داده‌های قرارداد در پاسخ API
/// </summary>
internal class DayaContractData
{
[JsonPropertyName("nationalCode")]
public string NationalCode { get; set; } = string.Empty;
[JsonPropertyName("applicationNo")]
public string ApplicationNo { get; set; } = string.Empty;
[JsonPropertyName("statusDescription")]
public string StatusDescription { get; set; } = string.Empty;
[JsonPropertyName("dateTime")]
public DateTime DateTime { get; set; }
}
#endregion

View File

@@ -13,7 +13,7 @@ COPY ["CMSMicroservice.Application/CMSMicroservice.Application.csproj", "CMSMicr
COPY ["CMSMicroservice.Domain/CMSMicroservice.Domain.csproj", "CMSMicroservice.Domain/"] COPY ["CMSMicroservice.Domain/CMSMicroservice.Domain.csproj", "CMSMicroservice.Domain/"]
COPY ["CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj", "CMSMicroservice.Infrastructure/"] COPY ["CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj", "CMSMicroservice.Infrastructure/"]
COPY ["CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj", "CMSMicroservice.Protobuf/"] COPY ["CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj", "CMSMicroservice.Protobuf/"]
RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile ../NuGet.config
COPY . . COPY . .
WORKDIR "/src/CMSMicroservice.WebApi" WORKDIR "/src/CMSMicroservice.WebApi"
RUN dotnet build "CMSMicroservice.WebApi.csproj" -c Release -o /app/build RUN dotnet build "CMSMicroservice.WebApi.csproj" -c Release -o /app/build

View File

@@ -115,7 +115,7 @@ public class DayaLoanCheckWorker
"daya-loan-check", "daya-loan-check",
worker => worker.ExecuteAsync(), worker => worker.ExecuteAsync(),
"*/15 * * * *", // هر 15 دقیقه "*/15 * * * *", // هر 15 دقیقه
TimeZoneInfo.Utc TimeZoneInfo.Local
); );
} }
} }

View File

@@ -44,6 +44,12 @@
"BaseUrl": "https://api.daya.ir", "BaseUrl": "https://api.daya.ir",
"ApiKey": "YOUR_DAYA_API_KEY" "ApiKey": "YOUR_DAYA_API_KEY"
}, },
"DayaApi": {
"UseMock": false,
"BaseAddress": "https://testdaya.tadbirandishan.com",
"MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq",
"CacheDurationMinutes": 20
},
"AllowedHosts": "*", "AllowedHosts": "*",
"Kestrel": { "Kestrel": {
"EndpointDefaults": { "EndpointDefaults": {