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": {