Files
docs/01-BUSINESS/daya-loan-integration.md
T
masoodafar-web 119e870a26 feat: Complete overhaul of FourSat documentation structure and content
- Added FINAL-STATUS.md detailing project completion and key metrics
- Created QUICK-REFERENCE.md for quick access to essential documents
- Updated README.md with project overview and quick start guide
- Established STRUCTURE.md outlining the final documentation structure
- Organized and archived old files, ensuring a clean and efficient directory
- Enhanced documentation quality with comprehensive metrics and checklists
2025-12-04 17:32:31 +03:30

19 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
{
    PendingReceive = 0,  // در انتظار دریافت وام
    Received = 1,        // وام دریافت شده (آینده)
    Rejected = 2         // رد شده (آینده)
}

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 فعلاً skeleton است و API واقعی دایا پیاده‌سازی نشده.


Infrastructure Layer

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

📋 Pending Tasks

High Priority

  • پیاده‌سازی API واقعی دایا در CheckDayaLoanStatusCommandHandler
  • اضافه کردن Proto definitions برای Daya commands
  • اضافه کردن 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