diff --git a/src/CMSMicroservice.Domain/Enums/DayaLoanStatus.cs b/src/CMSMicroservice.Domain/Enums/DayaLoanStatus.cs
index 3aaf798..aa693db 100644
--- a/src/CMSMicroservice.Domain/Enums/DayaLoanStatus.cs
+++ b/src/CMSMicroservice.Domain/Enums/DayaLoanStatus.cs
@@ -6,17 +6,28 @@ namespace CMSMicroservice.Domain.Enums;
public enum DayaLoanStatus
{
///
- /// در انتظار دریافت وام (خرید انجام شده، قرارداد امضا شده، درخواست وام ثبت شده)
+ /// هنوز درخواست نشده
///
- PendingReceive = 0,
+ NotRequested = 0,
///
- /// وام دریافت شده (در آینده اضافه میشود)
+ /// در انتظار دریافت وام (قرارداد امضا شده، در انتظار تسویه)
+ /// وضعیت: "فعال شده (در انتظار تسویه)"
///
- Received = 1,
+ PendingReceive = 1,
///
- /// رد شده (در آینده اضافه میشود)
+ /// وام دریافت شده و شارژ شده
///
- Rejected = 2,
+ Received = 2,
+
+ ///
+ /// رد شده
+ ///
+ Rejected = 3,
+
+ ///
+ /// در حال بررسی
+ ///
+ UnderReview = 4,
}
diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs
index 44dacb5..619c7a4 100644
--- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs
+++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs
@@ -32,7 +32,39 @@ public static class ConfigureServices
services.AddScoped();
services.AddScoped();
services.AddScoped();
- services.AddScoped(); // Mock - جایگزین با Real برای Production
+
+ // Daya Loan API Service - قابل تغییر بین Mock و Real
+ var useMockDayaApi = configuration.GetValue("DayaApi:UseMock", false);
+
+ if (useMockDayaApi)
+ {
+ // Mock برای Development/Testing
+ services.AddScoped();
+ }
+ else
+ {
+ // Real Implementation با HttpClient
+ services.AddHttpClient()
+ .SetHandlerLifetime(TimeSpan.FromMinutes(5))
+ .ConfigureHttpClient((sp, client) =>
+ {
+ var config = sp.GetRequiredService();
+
+ // 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)
var useRealPaymentGateway = configuration.GetValue("UseRealPaymentGateway", false);
diff --git a/src/CMSMicroservice.Infrastructure/Services/DayaLoanApiService.cs b/src/CMSMicroservice.Infrastructure/Services/DayaLoanApiService.cs
index 9afe2dd..9e5b233 100644
--- a/src/CMSMicroservice.Infrastructure/Services/DayaLoanApiService.cs
+++ b/src/CMSMicroservice.Infrastructure/Services/DayaLoanApiService.cs
@@ -1,11 +1,15 @@
using CMSMicroservice.Application.DayaLoanCQ.Services;
using CMSMicroservice.Domain.Enums;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
+using System.Text.Json.Serialization;
+using System.Linq;
namespace CMSMicroservice.Infrastructure.Services;
@@ -74,31 +78,232 @@ public class MockDayaLoanApiService : IDayaLoanApiService
///
/// Real Implementation برای API واقعی دایا
-/// TODO: این کلاس باید پیادهسازی شود زمانی که API دایا آماده شد
+/// مبتنی بر مستند TA-DAYA-S10-G-MerchantServices
///
public class DayaLoanApiService : IDayaLoanApiService
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
+ private readonly IConfiguration _configuration;
- public DayaLoanApiService(HttpClient httpClient, ILogger logger)
+ public DayaLoanApiService(
+ HttpClient httpClient,
+ ILogger logger,
+ IConfiguration configuration)
{
_httpClient = httpClient;
_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> CheckLoanStatusAsync(
List nationalCodes,
CancellationToken cancellationToken = default)
{
- // TODO: پیادهسازی واقعی API دایا
- // مثال:
- // var request = new DayaApiRequest { NationalCodes = nationalCodes };
- // var response = await _httpClient.PostAsJsonAsync("/api/loan/check", request, cancellationToken);
- // response.EnsureSuccessStatusCode();
- // var result = await response.Content.ReadFromJsonAsync(cancellationToken);
- // return MapToResults(result);
-
- throw new NotImplementedException("Real Daya API is not implemented yet. Use MockDayaLoanApiService for testing.");
+ try
+ {
+ _logger.LogInformation("Calling Daya API for {Count} national codes", nationalCodes.Count);
+
+ // ساخت 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(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);
+ }
+ }
+
+ ///
+ /// تبدیل پاسخ API دایا به مدل داخلی
+ ///
+ private List MapApiResponseToResults(
+ List apiData,
+ List requestedNationalCodes)
+ {
+ var results = new List();
+
+ // برای هر کد ملی درخواستی
+ 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;
+ }
+
+ ///
+ /// تبدیل StatusDescription دایا به Enum داخلی
+ ///
+ 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;
+ }
+
+ ///
+ /// ساخت نتایج خالی در صورت خطا
+ ///
+ private List CreateEmptyResults(List nationalCodes)
+ {
+ return nationalCodes.Select(nc => new DayaLoanStatusResult
+ {
+ NationalCode = nc,
+ Status = DayaLoanStatus.NotRequested,
+ ContractNumber = null
+ }).ToList();
}
}
+
+#region Daya API Models
+
+///
+/// Request body برای /api/merchant/contracts
+///
+internal class DayaContractsRequest
+{
+ [JsonPropertyName("NationalCodes")]
+ public List NationalCodes { get; set; } = new();
+}
+
+///
+/// Response از /api/merchant/contracts
+///
+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? Data { get; set; }
+}
+
+///
+/// دادههای قرارداد در پاسخ API
+///
+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
diff --git a/src/CMSMicroservice.WebApi/Dockerfile b/src/CMSMicroservice.WebApi/Dockerfile
index d1fad2c..ad54a5e 100644
--- a/src/CMSMicroservice.WebApi/Dockerfile
+++ b/src/CMSMicroservice.WebApi/Dockerfile
@@ -13,7 +13,7 @@ COPY ["CMSMicroservice.Application/CMSMicroservice.Application.csproj", "CMSMicr
COPY ["CMSMicroservice.Domain/CMSMicroservice.Domain.csproj", "CMSMicroservice.Domain/"]
COPY ["CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj", "CMSMicroservice.Infrastructure/"]
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 . .
WORKDIR "/src/CMSMicroservice.WebApi"
RUN dotnet build "CMSMicroservice.WebApi.csproj" -c Release -o /app/build
diff --git a/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs b/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs
index d727300..79ed217 100644
--- a/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs
+++ b/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs
@@ -115,7 +115,7 @@ public class DayaLoanCheckWorker
"daya-loan-check",
worker => worker.ExecuteAsync(),
"*/15 * * * *", // هر 15 دقیقه
- TimeZoneInfo.Utc
+ TimeZoneInfo.Local
);
}
}
diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json
index 8f15b9c..1313462 100644
--- a/src/CMSMicroservice.WebApi/appsettings.json
+++ b/src/CMSMicroservice.WebApi/appsettings.json
@@ -44,6 +44,12 @@
"BaseUrl": "https://api.daya.ir",
"ApiKey": "YOUR_DAYA_API_KEY"
},
+ "DayaApi": {
+ "UseMock": false,
+ "BaseAddress": "https://testdaya.tadbirandishan.com",
+ "MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq",
+ "CacheDurationMinutes": 20
+ },
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {