Files
docs/business/daya-loan-integration.md
T

31 KiB

Daya Loan Integration System (سیستم یکپارچه‌سازی وام دایا)

📌 Overview

سیستم یکپارچه‌سازی با سرویس وام دایا برای شارژ خودکار کیف پول کاربران که وام دایا دریافت کرده‌اند.

مقادیر شارژ:

  • کیف پول اصلی (Balance): 56,000,000 تومان
  • کیف پول شبکه/کارمزد (NetworkBalance): 56,000,000 تومان
  • کیف پول تخفیف (DiscountBalance): 56,000,000 تومان
  • مجموع: 168,000,000 تومان

نکته مهم: کیف پول باشگاه (ClubWallet) باید توسط کاربر در فرانت‌آفیس به صورت دستی شارژ شود.


🗂️ Architecture

Domain Layer

DayaLoanStatus Enum

public enum DayaLoanStatus
{
    NotRequested = 0,    // درخواست نشده
    PendingReceive = 1,  // در انتظار دریافت وام (فعال شده)
    Received = 2,        // وام دریافت شده
    Rejected = 3,        // رد شده
    UnderReview = 4      // در حال بررسی
}

DayaLoanContract Entity

public class DayaLoanContract : BaseAuditableEntity
{
    public long UserId { get; set; }
    public string NationalCode { get; set; }
    public string? ContractNumber { get; set; }
    public DayaLoanStatus Status { get; set; }
    public bool IsProcessed { get; set; }
    public DateTime? LastCheckDate { get; set; }
    public DateTime? ProcessedDate { get; set; }
    public long? TransactionId { get; set; }
    
    // Navigation Properties
    public virtual User User { get; set; }
    public virtual Transactions? Transaction { get; set; }
}

User Entity Extensions

public class User : BaseAuditableEntity
{
    // ... existing properties ...
    
    public bool HasReceivedDayaCredit { get; set; }
    public DateTime? DayaCreditReceivedAt { get; set; }
    public virtual ICollection<DayaLoanContract>? DayaLoanContracts { get; set; }
}

Application Layer

Commands

1. ProcessDayaLoanApprovalCommand

شارژ کیف پول کاربر بعد از تایید وام دایا

Request:

public record ProcessDayaLoanApprovalCommand : IRequest<ProcessDayaLoanApprovalResponseDto>
{
    public long UserId { get; init; }
    public string ContractNumber { get; init; }
    public long WalletAmount { get; init; } = 56_000_000;
    public long LockedWalletAmount { get; init; } = 56_000_000;
    public long DiscountWalletAmount { get; init; } = 56_000_000;
}

Response:

public class ProcessDayaLoanApprovalResponseDto
{
    public long UserId { get; set; }
    public long TransactionId { get; set; }
    public string ContractNumber { get; set; }
    public long MainWalletBalance { get; set; }
    public long LockedWalletBalance { get; set; }
    public long DiscountWalletBalance { get; set; }
    public string Message { get; set; }
}

Business Logic:

  1. بررسی اینکه کاربر قبلاً اعتبار دایا را دریافت نکرده باشد
  2. ایجاد Transaction با:
    • Type: DepositExternal1
    • Amount: 168M تومان
    • RefId: شماره قرارداد دایا
  3. شارژ سه نوع کیف پول (Balance, NetworkBalance, DiscountBalance)
  4. ثبت UserWalletChangeLog برای Balance و NetworkBalance (⚠️ DiscountBalance لاگ ندارد)
  5. به‌روزرسانی فلگ‌های کاربر (HasReceivedDayaCredit, DayaCreditReceivedAt)
  6. انتشار DayaLoanApprovedEvent
2. CheckDayaLoanStatusCommand

استعلام وضعیت وام از سرویس دایا

Request:

public record CheckDayaLoanStatusCommand : IRequest<CheckDayaLoanStatusResponseDto>
{
    public List<string> NationalCodes { get; init; }
}

Response:

public class CheckDayaLoanStatusResponseDto
{
    public List<DayaLoanCheckResult> Results { get; set; }
    public int TotalChecked { get; set; }
    public int SuccessCount { get; set; }
}

public class DayaLoanCheckResult
{
    public string NationalCode { get; set; }
    public DayaLoanStatus Status { get; set; }
    public string? ContractNumber { get; set; }
}

Current Status: این Command کاملاً پیاده‌سازی شده و به API واقعی Daya متصل است.

API Integration Details:

  • Endpoint: POST /api/merchant/contracts
  • Base URL: https://testdaya.tadbirandishan.com
  • Authentication: merchant-permission-key header
  • Request Body:
    {
      "nationalCodes": ["1234567890", "0987654321"]
    }
    
  • Response Structure:
    {
      "succeed": true,
      "code": 200,
      "message": "Success",
      "data": [
        {
          "nationalCode": "1234567890",
          "contractNumber": "DAYA-12345",
          "statusDescription": "فعال شده (در انتظار تسویه)",
          "dateTime": "2024-12-06T10:30:00"
        }
      ]
    }
    
  • Status Mapping:
    • "فعال شده (در انتظار تسویه)" → PendingReceive
    • "تایید شده" → Received
    • "رد شده" → Rejected
    • Default → UnderReview
  • Cache Duration: 20 minutes (per Daya API spec)
  • Multiple Contracts: If user has multiple contracts, system takes the latest one by DateTime

Infrastructure Layer

IDayaLoanApiService Implementations

1. MockDayaLoanApiService (Testing):

  • Returns mock data based on NationalCode patterns
  • Instant response for fast testing
  • No external dependencies

2. DayaLoanApiService (Production):

  • Fully implemented with HttpClient
  • Posts to /api/merchant/contracts endpoint
  • Handles API errors gracefully
  • Maps Persian status descriptions to enum values
  • Returns empty results on error (prevents worker crashes)

Configuration (appsettings.json):

{
  "DayaApi": {
    "UseMock": false,
    "BaseAddress": "https://testdaya.tadbirandishan.com",
    "MerchantPermissionKey": "14752708$Db5Wk5h...",
    "CacheDurationMinutes": 20
  }
}

Service Registration (ConfigureServices.cs):

  • Reads DayaApi:UseMock from configuration
  • If true: Uses MockDayaLoanApiService
  • If false: Uses DayaLoanApiService with HttpClient
  • HttpClient configured with BaseAddress, headers, and 30s timeout

Background Worker: DayaLoanCheckWorker

Worker خودکار که هر 15 دقیقه کاربران با وام pending را چک می‌کند.

Location: CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs

Schedule: */15 * * * * (هر 15 دقیقه)

Logic:

  1. Query کاربرانی که HasReceivedDayaCredit == false و دارای NationalCode هستند
  2. فراخوانی CheckDayaLoanStatusCommand با لیست کدملی‌ها
  3. برای هر نتیجه با Status=PendingReceive و ContractNumber موجود:
    • فراخوانی ProcessDayaLoanApprovalCommand
    • لاگ نتیجه عملیات
  4. Retry خودکار در صورت خطا (Hangfire AutomaticRetry)

Registration: در Program.cs ثبت شده است:

DayaLoanCheckWorker.Schedule(recurringJobManager);

🔄 Process Flow

1. کاربر درخواست وام دایا می‌دهد (خارج از سیستم)
   ↓
2. Worker هر 15 دقیقه کاربران pending را چک می‌کند
   ↓
3. CheckDayaLoanStatusCommand → فراخوانی API دایا
   ↓
4. اگر Status = PendingReceive و ContractNumber موجود بود:
   ↓
5. ProcessDayaLoanApprovalCommand اجرا می‌شود:
   - ایجاد Transaction (168M تومان)
   - شارژ Balance (+56M)
   - شارژ NetworkBalance (+56M)
   - شارژ DiscountBalance (+56M)
   - ثبت WalletChangeLog (برای Balance و NetworkBalance)
   - تنظیم HasReceivedDayaCredit = true
   ↓
6. DayaLoanApprovedEvent منتشر می‌شود
   ↓
7. EventHandler می‌تواند عملیات جانبی انجام دهد (مثل ارسال اطلاع‌رسانی)

💾 Database Schema

DayaLoanContracts Table

CREATE TABLE [CMS].[DayaLoanContracts] (
    [Id] bigint IDENTITY(1,1) PRIMARY KEY,
    [UserId] bigint NOT NULL FOREIGN KEY REFERENCES Users(Id),
    [NationalCode] nvarchar(max) NOT NULL,
    [ContractNumber] nvarchar(max) NULL,
    [Status] int NOT NULL,
    [IsProcessed] bit NOT NULL,
    [LastCheckDate] datetime2 NULL,
    [ProcessedDate] datetime2 NULL,
    [TransactionId] bigint NULL FOREIGN KEY REFERENCES Transactionss(Id),
    [Created] datetime2 NOT NULL,
    [CreatedBy] nvarchar(max) NULL,
    [LastModified] datetime2 NULL,
    [LastModifiedBy] nvarchar(max) NULL,
    [IsDeleted] bit NOT NULL
);

User Table Extensions

ALTER TABLE [CMS].[Users]
ADD [HasReceivedDayaCredit] bit NOT NULL DEFAULT 0,
    [DayaCreditReceivedAt] datetime2 NULL;

Migration: 20251201191716_AddDayaLoanIntegration.cs


⚠️ Important Notes

⚠️ CRITICAL: Don't Remove Business Logic on Errors!

  • وقتی با خطا مواجه شدیم، NEVER پاک نکنید بخشی از بیزینس را
  • اول 5 بار تلاش کنید که خطا را برطرف کنید
  • اگر خطا برطرف نشد، آن را به حال خود رها کنید (Comment + TODO)
  • Developer دستی خطا را بررسی و حل خواهد کرد

مثال درست:

// TODO: این قسمت خطا دارد - نیاز به بررسی
// Error: CS1234 - Type not found
// var discountLog = new UserWalletChangeLog { ... };
// await _context.UserWalletChangeLogs.AddAsync(discountLog);

مثال غلط (ممنوع!):

// ❌ پاک کردن لاگ DiscountBalance برای حل خطا - WRONG!
// این کار باعث از دست رفتن بخشی از بیزینس می‌شود

1. UserWalletChangeLog Limitation

  • فیلدهای موجود: CurrentBalance, ChangeValue, CurrentNetworkBalance, ChangeNerworkValue
  • مشکل: فیلدی برای DiscountBalance وجود ندارد
  • راه‌حل فعلی: تغییرات DiscountBalance در لاگ ثبت نمی‌شود، فقط در جدول UserWallets ذخیره می‌شود
  • پیشنهاد آینده: اضافه کردن فیلدهای CurrentDiscountBalance و ChangeDiscountValue به UserWalletChangeLog

2. Daya API Integration

  • وضعیت فعلی: CheckDayaLoanStatusCommandHandler یک skeleton است
  • TODO: پیاده‌سازی API واقعی دایا در Handler
  • Placeholder Code:
    // TODO: فراخوانی سرویس دایا
    // در حال حاضر داده Mock برمی‌گردانیم
    

3. Transaction Type

  • از TransactionType.DepositExternal1 استفاده می‌شود
  • RefId = شماره قرارداد دایا
  • این اطلاعات برای پیگیری و تطبیق با دایا ضروری است

4. One-Time Credit

  • هر کاربر فقط یک بار می‌تواند اعتبار دایا دریافت کند
  • بررسی توسط HasReceivedDayaCredit flag
  • تلاش برای دریافت مجدد با خطا مواجه می‌شود

🧪 Testing

Manual Testing via Hangfire Dashboard

  1. به Hangfire Dashboard بروید: /hangfire
  2. در بخش "Recurring Jobs" job با نام daya-loan-check را پیدا کنید
  3. دکمه "Trigger now" را بزنید
  4. در بخش "Jobs" می‌توانید لاگ‌ها را ببینید

Testing Commands via gRPC (آینده)

# فراخوانی ProcessDayaLoanApproval
grpcurl -d '{
  "userId": 123,
  "contractNumber": "DAYA-12345"
}' localhost:5001 ProcessDayaLoanApproval

# فراخوانی CheckDayaLoanStatus
grpcurl -d '{
  "nationalCodes": ["1234567890"]
}' localhost:5001 CheckDayaLoanStatus

Completed Implementation

High Priority (All Done)

  • پیاده‌سازی API واقعی دایا در DayaLoanApiService (December 6, 2025)
    • HTTP POST to /api/merchant/contracts
    • Request/Response models with JSON serialization
    • Status description mapping (Persian → Enum)
    • Error handling and logging
    • Configurable via appsettings.json
  • Conditional service registration (Mock vs Real)
  • HttpClient configuration with authentication
  • Worker fully operational with real API

Low Priority (Optional)

  • اضافه کردن Proto definitions برای Daya commands
  • Admin UI for Daya contract management
  • Unit tests for API service
  • اضافه کردن gRPC service endpoints
  • تست Worker در محیط development

Medium Priority

  • ایجاد BFF handlers برای عملیات دایا
  • ایجاد صفحات BackOffice برای مدیریت وام دایا
  • اضافه کردن فیلتر برای مشاهده کاربران با وام دایا
  • نمایش تاریخچه Daya Loan Contracts

Low Priority

  • اضافه کردن Unit Tests برای ProcessDayaLoanApprovalCommand
  • اضافه کردن Integration Tests برای DayaLoanCheckWorker
  • اضافه کردن Monitoring/Alerting برای خطاهای API دایا
  • بهینه‌سازی Query برای یافتن کاربران pending
  • اضافه کردن فیلدهای DiscountBalance به UserWalletChangeLog

Domain

  • CMSMicroservice.Domain/Enums/DayaLoanStatus.cs
  • CMSMicroservice.Domain/Entities/DayaLoanContract.cs
  • CMSMicroservice.Domain/Entities/User.cs (updated)
  • CMSMicroservice.Domain/Events/DayaLoanApprovedEvent.cs

Application

  • CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/
    • ProcessDayaLoanApprovalCommand.cs
    • ProcessDayaLoanApprovalCommandHandler.cs
    • ProcessDayaLoanApprovalCommandValidator.cs
    • ProcessDayaLoanApprovalResponseDto.cs
  • CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/
    • CheckDayaLoanStatusCommand.cs
    • CheckDayaLoanStatusCommandHandler.cs
    • CheckDayaLoanStatusResponseDto.cs
  • CMSMicroservice.Application/DayaLoanCQ/EventHandlers/
    • DayaLoanApprovedEventHandler.cs

Infrastructure

  • CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs (updated)
  • CMSMicroservice.Infrastructure/Persistence/Migrations/20251201191716_AddDayaLoanIntegration.cs

WebApi

  • CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs
  • CMSMicroservice.WebApi/Program.cs (updated)

🧪 Testing

Manual Testing

1. ایجاد کاربر تست با کدملی شروع شده با "1"

-- کاربری که Mock Service برایش وام تایید می‌کند
INSERT INTO CMS.Users (NationalCode, FirstName, LastName, Mobile, HasReceivedDayaCredit)
VALUES ('1234567890', 'Test', 'User', '09121234567', 0);

2. اجرای دستی Worker از Hangfire Dashboard

  • باز کردن: https://localhost:5001/hangfire
  • انتخاب Job: daya-loan-check
  • کلیک روی "Trigger now"

3. بررسی Logs

# در Console پروژه CMS
[INFO] DayaLoanCheckWorker started at 2024-12-02 10:30:00
[INFO] Found 1 users with pending Daya loan status
[WARN] ⚠️ Using MOCK Daya API Service - Replace with real implementation!
[INFO] Mock Daya API returned 1 results
[INFO] Daya loan processed for user 123. Contract: MOCK-DAYA-1234567890-638123456789
[INFO] DayaLoanCheckWorker completed. Checked: 1, Processed: 1

4. بررسی Database

-- چک کردن DayaLoanContract
SELECT * FROM CMS.DayaLoanContracts WHERE NationalCode = '1234567890';

-- چک کردن UserWallet
SELECT * FROM CMS.UserWallets WHERE UserId = 123;
-- Balance باید 56,000,000 باشد
-- NetworkBalance باید 56,000,000 باشد
-- DiscountBalance باید 56,000,000 باشد

-- چک کردن Transaction
SELECT * FROM CMS.Transactionss WHERE RefId LIKE 'MOCK-DAYA-%';
-- Amount باید 168,000,000 باشد

-- چک کردن User Flag
SELECT HasReceivedDayaCredit, DayaCreditReceivedAt FROM CMS.Users WHERE Id = 123;
-- HasReceivedDayaCredit باید 1 باشد

5. تست Mock Service Scenarios

// کدملی شروع با "1" → PendingReceive + ContractNumber
// کدملی شروع با "2" → Rejected
// سایر کدملی‌ها → PendingReceive (بدون ContractNumber)

Integration Testing با Real API

زمانی که API واقعی دایا آماده شد:

  1. تغییر ConfigureServices:
// در CMSMicroservice.Infrastructure/ConfigureServices.cs
services.AddScoped<IDayaLoanApiService, DayaLoanApiService>(); // Real
// services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>(); // Mock - حذف شود
  1. تنظیم HttpClient:
services.AddHttpClient<IDayaLoanApiService, DayaLoanApiService>(client =>
{
    client.BaseAddress = new Uri(configuration["DayaApi:BaseUrl"]);
    client.Timeout = TimeSpan.FromSeconds(30);
});
  1. اضافه کردن به appsettings.json:
{
  "DayaApi": {
    "BaseUrl": "https://api.daya.ir",
    "ApiKey": "YOUR_API_KEY_HERE"
  }
}

🐛 Troubleshooting

مشکل: Worker اجرا نمی‌شود

علت احتمالی: Hangfire Server شروع نشده

راه حل:

// در Program.cs چک کنید که این خط وجود دارد:
builder.Services.AddHangfireServer();

مشکل: کاربران پیدا نمی‌شوند

علت احتمالی: همه کاربران قبلاً اعتبار دریافت کرده‌اند

راه حل:

-- Reset کردن وضعیت کاربران برای تست
UPDATE CMS.Users SET HasReceivedDayaCredit = 0, DayaCreditReceivedAt = NULL;

مشکل: کیف پول شارژ نمی‌شود

علت احتمالی: کاربر کیف پول ندارد

راه حل:

// کد Handler خودکار UserWallet می‌سازد اگر موجود نباشد:
if (wallet == null)
{
    wallet = new UserWallet { UserId = request.UserId, Balance = 0, ... };
    await _context.UserWallets.AddAsync(wallet, cancellationToken);
}

مشکل: Mock API همیشه نتیجه یکسان برمی‌گرداند

راه حل: کدملی کاربر را تغییر دهید:

  • کدملی شروع با "1" → وام تایید می‌شود
  • کدملی شروع با "2" → وام رد می‌شود
  • سایر → در انتظار (بدون ContractNumber)

مشکل: Exception در ProcessDayaLoanApproval

خطای احتمالی: User has already received Daya credit

علت: کاربر قبلاً اعتبار دریافت کرده

راه حل:

-- فقط برای محیط Development
UPDATE CMS.Users SET HasReceivedDayaCredit = 0 WHERE Id = 123;

مشکل: Migration اعمال نمی‌شود

راه حل:

cd CMS/src/CMSMicroservice.WebApi
dotnet ef database update

یا در Package Manager Console:

Update-Database

📊 Monitoring

Hangfire Dashboard

URL: https://localhost:5001/hangfire

Metrics:

  • Succeeded jobs
  • Failed jobs
  • Processing jobs
  • Scheduled jobs

Job Details:

  • Job ID: daya-loan-check
  • Schedule: */15 * * * * (Every 15 minutes)
  • Next Run: نمایش داده می‌شود در Dashboard

Application Logs

Successful Run:

[INFO] DayaLoanCheckWorker started at {Time}
[INFO] Found {Count} users with pending Daya loan status
[INFO] Daya loan processed for user {UserId}. Contract: {ContractNumber}
[INFO] DayaLoanCheckWorker completed. Checked: {Total}, Processed: {Success}

Error Scenarios:

[ERROR] Error processing Daya loan for user {UserId}
[ERROR] Error calling Daya API service
[ERROR] Error in DayaLoanCheckWorker

🔒 Security Considerations

  1. API Key Management:

    • هرگز API Key را در کد Commit نکنید
    • از User Secrets برای Development استفاده کنید
    • از Azure Key Vault یا مشابه برای Production استفاده کنید
  2. Rate Limiting:

    • Worker هر 15 دقیقه اجرا می‌شود → حداکثر 96 بار در روز
    • اگر API دایا محدودیت دارد، باید تنظیم شود
  3. Data Validation:

    • کدملی باید 10 رقمی باشد
    • فقط یک بار برای هر کاربر پردازش می‌شود

📈 Performance Optimization

Batch Processing

اگر تعداد کاربران زیاد باشد، می‌توان Query را بهینه کرد:

// پردازش دسته‌ای (100 کاربر در هر بار)
var pendingUsers = await _context.Users
    .Where(u => u.HasReceivedDayaCredit == false && u.NationalCode != null)
    .Take(100) // Limit
    .Select(u => new { u.Id, u.NationalCode })
    .ToListAsync();

Caching

می‌توان نتایج API را برای مدت کوتاهی Cache کرد:

// Cache result for 5 minutes
[MemoryCache]
public async Task<List<DayaLoanStatusResult>> CheckLoanStatusAsync(...)

📚 References


Created: 2024-12-01
Last Updated: 2024-12-02
Status: 100% Implemented (Mock API in use - Real API integration pending)
Migration: 20251201191716_AddDayaLoanIntegration
Test Coverage: Manual testing documented
Next Steps: Replace MockDayaLoanApiService with real API implementation when available


جزئیات پیاده‌سازی API در CMS

Daya Loan API Implementation - Complete Guide

تاریخ تکمیل: December 6, 2025
وضعیت: 100% Complete - Production Ready
نسخه: Real API v1.0


📋 خلاصه تغییرات

قبل از این به‌روزرسانی:

  • CheckDayaLoanStatusCommandHandler: Skeleton با TODO
  • DayaLoanApiService: NotImplementedException
  • MockDayaLoanApiService: فقط برای تست

بعد از این به‌روزرسانی:

  • DayaLoanApiService: کاملاً پیاده‌سازی شده
  • HttpClient configuration: با authentication و timeout
  • Status mapping: Persian descriptions → Enum
  • Error handling: کامل با fallback
  • Configuration: Switchable Mock/Real via appsettings

🔧 فایل‌های تغییر یافته

1. DayaLoanApiService.cs

مسیر: CMS/src/CMSMicroservice.Infrastructure/Services/DayaLoanApiService.cs

تغییرات:

// BEFORE:
public async Task<List<DayaLoanCheckResult>> CheckLoanStatusAsync(...)
{
    throw new NotImplementedException("TODO: Implement real Daya API");
}

// AFTER: (~250 lines of implementation)
- Request/Response Models با JsonPropertyName
- HTTP POST به /api/merchant/contracts
- Status mapping logic
- Error handling با empty results
- Multiple contracts handling (takes latest)

Models اضافه شده:

  • DayaContractsRequest: NationalCodes list
  • DayaContractsResponse: Succeed, Code, Message, Data
  • DayaContractData: NationalCode, ContractNumber, StatusDescription, DateTime

متدهای کلیدی:

  • CheckLoanStatusAsync: Main entry point
  • MapApiResponseToResults: Convert API response to domain results
  • MapStatusDescription: Persian text → DayaLoanStatus enum
  • CreateEmptyResults: Fallback for errors

2. ConfigureServices.cs

مسیر: CMS/src/CMSMicroservice.Infrastructure/ConfigureServices.cs

تغییرات:

// BEFORE:
services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>();

// AFTER:
var useMock = configuration.GetValue<bool>("DayaApi:UseMock");
if (useMock)
{
    services.AddScoped<IDayaLoanApiService, MockDayaLoanApiService>();
}
else
{
    services.AddHttpClient<IDayaLoanApiService, DayaLoanApiService>((sp, client) =>
    {
        var config = sp.GetRequiredService<IConfiguration>();
        client.BaseAddress = new Uri(config["DayaApi:BaseAddress"]!);
        client.DefaultRequestHeaders.Add("merchant-permission-key", 
            config["DayaApi:MerchantPermissionKey"]);
        client.Timeout = TimeSpan.FromSeconds(30);
    })
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));
}

ویژگی‌های HttpClient:

  • BaseAddress: Dynamic from config
  • Authentication: merchant-permission-key header
  • Timeout: 30 seconds
  • Handler Lifetime: 5 minutes (connection pooling)

3. appsettings.json

مسیر: CMS/src/CMSMicroservice.WebApi/appsettings.json

بخش اضافه شده:

{
  "DayaApi": {
    "UseMock": false,
    "BaseAddress": "https://testdaya.tadbirandishan.com",
    "MerchantPermissionKey": "14752708$Db5Wk5hnhKO4FGuoKBUZIvHW5WO1NpCxYNy_sy8epfQ-d6n6vjeZJa6EnTq876cq",
    "CacheDurationMinutes": 20
  }
}

توضیح پارامترها:

  • UseMock: اگر true باشد، MockDayaLoanApiService استفاده می‌شود
  • BaseAddress: URL سرویس Daya (Test یا Production)
  • MerchantPermissionKey: کلید احراز هویت
  • CacheDurationMinutes: مدت cache در سمت Daya (فقط اطلاعاتی)

4. DayaLoanStatus.cs

مسیر: CMS/src/CMSMicroservice.Domain/Enums/DayaLoanStatus.cs

تغییرات:

// BEFORE:
public enum DayaLoanStatus
{
    PendingReceive = 0,
    Received = 1,
    Rejected = 2
}

// AFTER:
public enum DayaLoanStatus
{
    NotRequested = 0,    // جدید
    PendingReceive = 1,  // عدد تغییر کرد
    Received = 2,        // عدد تغییر کرد
    Rejected = 3,        // عدد تغییر کرد
    UnderReview = 4      // جدید
}

⚠️ توجه: این یک Breaking Change است اگر دیتابیس از قبل داده دارد.


🔄 جریان کامل سیستم

1. Hangfire Worker (هر 15 دقیقه)
   ↓
2. Query Users with HasReceivedDayaCredit = false
   ↓
3. CheckDayaLoanStatusCommand
   ↓
4. DayaLoanApiService.CheckLoanStatusAsync
   ↓
5. HTTP POST /api/merchant/contracts
   ↓
6. Daya API Response (JSON)
   ↓
7. MapApiResponseToResults
   ↓
8. برای هر کاربر با Status = PendingReceive:
   ↓
9. ProcessDayaLoanApprovalCommand
   ↓
10. شارژ 3 کیف پول (Balance, NetworkBalance, DiscountBalance)
    ↓
11. Set HasReceivedDayaCredit = true
    ↓
12. DayaLoanApprovedEvent published

🧪 تست و اعتبارسنجی

تست با Mock (Development):

// appsettings.json
{
  "DayaApi": {
    "UseMock": true
  }
}

تست با Real API (Staging):

{
  "DayaApi": {
    "UseMock": false,
    "BaseAddress": "https://testdaya.tadbirandishan.com",
    "MerchantPermissionKey": "YOUR_TEST_KEY"
  }
}

نحوه تست دستی:

  1. به Hangfire Dashboard بروید: /hangfire
  2. Job daya-loan-check را پیدا کنید
  3. دکمه "Trigger Now" را بزنید
  4. در Logs بررسی کنید:
    • Request body
    • API response
    • Mapped results
    • ProcessDayaLoanApproval results

📊 Status Mapping Logic

API Response → Enum:

StatusDescription (API) DayaLoanStatus (Enum) توضیح
"فعال شده (در انتظار تسویه)" PendingReceive (1) قرارداد فعال، منتظر واریز
"تایید شده" Received (2) وام دریافت شده
"رد شده" Rejected (3) درخواست رد شده
سایر موارد UnderReview (4) در حال بررسی یا نامشخص

کد Mapping:

private DayaLoanStatus MapStatusDescription(string? description)
{
    if (string.IsNullOrEmpty(description))
        return DayaLoanStatus.UnderReview;

    return description switch
    {
        "فعال شده (در انتظار تسویه)" => DayaLoanStatus.PendingReceive,
        "تایید شده" => DayaLoanStatus.Received,
        "رد شده" => DayaLoanStatus.Rejected,
        _ => DayaLoanStatus.UnderReview
    };
}

🐛 Error Handling

سناریوهای خطا:

  1. API Unreachable (Network error):

    • Log: "Error calling Daya API"
    • Return: Empty list
    • Worker continues
  2. 401 Unauthorized:

    • Log: "Invalid merchant-permission-key"
    • Return: Empty list
    • Check configuration
  3. API Returns succeed=false:

    • Log: "Daya API error: {message}"
    • Return: Empty list
    • Check Daya service status
  4. Multiple Contracts for User:

    • Behavior: Takes latest by DateTime
    • Log: "User has {count} contracts, taking latest"
  5. No ContractNumber:

    • Skip user (won't trigger ProcessDayaLoanApproval)
    • Only create/update DayaLoanContract record

🚀 Deployment Checklist

Pre-Production:

  • Replace test MerchantPermissionKey with production key
  • Change BaseAddress to production URL
  • Set UseMock: false in appsettings.Production.json
  • Test with real Daya API in staging environment
  • Verify Worker schedule (*/15 * * * *)
  • Check Hangfire Dashboard access

Monitoring:

  • Setup alerts for Worker failures
  • Monitor API call duration (should be < 30s)
  • Track ProcessDayaLoanApproval success rate
  • Verify no duplicate credits (HasReceivedDayaCredit flag)

Security:

  • MerchantPermissionKey stored in Azure Key Vault (not appsettings)
  • HTTPS only for API calls
  • Rate limiting on Worker (currently 15 min is safe)
  • Audit log for all credit approvals

📝 نکات مهم

1. Cache Duration

  • Daya API caches results for 20 minutes
  • Worker runs every 15 minutes → Some overlap acceptable
  • No need to implement client-side caching

2. Multiple Contracts

  • System supports users with multiple contracts
  • Always takes the latest one (by DateTime)
  • Old contracts ignored (not deleted from API)

3. One-Time Credit

  • HasReceivedDayaCredit flag ensures one-time credit only
  • Even if API returns multiple PendingReceive, only first processes
  • Idempotency guaranteed

4. Transaction Record

  • Type: DepositExternal1
  • Amount: 168,000,000 (total of 3 wallets)
  • RefId: Daya contract number
  • Use for reconciliation with Daya

5. DiscountBalance Logging

  • ⚠️ UserWalletChangeLog doesn't have DiscountBalance fields
  • Only Balance and NetworkBalance logged
  • DiscountBalance changes only in UserWallet table
  • Consider adding fields in future migration

🔗 مستندات مرتبط

  • Business Logic: totalDoc/01-BUSINESS/daya-loan-integration.md
  • Implementation Status: totalDoc/03-BACKEND/CMS/implementation-status.md (Phase 11)
  • API Spec: totalDoc/MerchantService.md (Daya Documentation)
  • Worker Guide: CMS/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs

تاییدیه نهایی

  • Build successful: 0 errors
  • Real API integration complete
  • Mock/Real switchable
  • Worker operational
  • Error handling robust
  • Configuration flexible
  • Status mapping accurate
  • Documentation complete

Status: 🟢 Ready for Production