diff --git a/.agent/skills/mermaid-diagrams/SKILL.md b/.agent/skills/mermaid-diagrams/SKILL.md
new file mode 100644
index 00000000..8d06ca76
--- /dev/null
+++ b/.agent/skills/mermaid-diagrams/SKILL.md
@@ -0,0 +1,338 @@
+---
+name: mermaid-diagrams
+description: Mermaid diagram patterns cho documentation. Use for flowcharts, sequence diagrams, class diagrams, ER diagrams, system architecture, hoặc khi cần visualize processes và relationships trong markdown.
+compatibility: "Mermaid JS, GitHub Markdown, VS Code"
+metadata:
+ author: Velik Ho
+ version: "1.0"
+ references: "mermaid.js.org"
+---
+
+# Mermaid Diagrams / Sơ đồ Mermaid
+
+## When to Use This Skill / Khi Nào Sử Dụng
+
+Use this skill when:
+- Visualizing processes, workflows, decision trees / Tạo sơ đồ quy trình, cây quyết định
+- Documenting API flows, service interactions / Tài liệu API flows, tương tác giữa services
+- Designing database schemas / Thiết kế database schema
+- Creating system architecture diagrams / Tạo sơ đồ kiến trúc hệ thống
+- Project timelines and planning / Timeline dự án
+
+## Quick Reference / Tham Chiếu Nhanh
+
+| Loại Sơ đồ | Sử dụng cho | Keyword |
+|------------|-------------|---------|
+| **Flowchart** | Quy trình, cây quyết định | `flowchart TD/LR` |
+| **Sequence** | API flows, request/response | `sequenceDiagram` |
+| **Class** | Code structure, patterns | `classDiagram` |
+| **Graph** | System architecture | `graph TD/LR` |
+| **ER** | Database schema | `erDiagram` |
+| **Gantt** | Timeline, scheduling | `gantt` |
+| **C4** | System context | `C4Context` |
+
+---
+
+## Core Patterns / Mẫu Chính
+
+### 1. Flowchart
+
+```mermaid
+flowchart TD
+ Start([Start]) --> Input[Get Input]
+ Input --> Check{Valid?}
+ Check -->|Yes| Process[Process Data]
+ Check -->|No| Error[Show Error]
+ Process --> Output[Return Result]
+ Output --> End([End])
+ Error --> End
+
+ style Start fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
+ style End fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:3px
+ style Check fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style Error fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
+ style Process fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+```
+
+**Subgraphs for grouping:**
+
+```mermaid
+flowchart LR
+ A[Request] --> B{Auth?}
+ B -->|No| C[401]
+ B -->|Yes| D[Process]
+
+ subgraph Processing["Request Processing"]
+ D --> E[Validate]
+ E --> F[Execute]
+ end
+
+ F --> G[200 OK]
+```
+
+### 2. Sequence Diagram
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant API
+ participant Service
+ participant DB
+
+ Client->>API: POST /login
+ API->>Service: authenticate(credentials)
+ Service->>DB: findUser(email)
+ DB-->>Service: user
+ Service-->>API: JWT token
+ API-->>Client: 200 OK {token}
+```
+
+**With Alt/Opt blocks:**
+
+```mermaid
+sequenceDiagram
+ Client->>API: GET /users/:id
+ API->>Cache: get(key)
+
+ alt Cache Hit
+ Cache-->>API: cached data
+ else Cache Miss
+ API->>DB: SELECT * FROM users
+ DB-->>API: user data
+ API->>Cache: set(key, data, ttl)
+ end
+
+ API-->>Client: 200 OK
+```
+
+### 3. Class Diagram
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class BaseRepository {
+ #prisma: PrismaClient
+ +findById(id: string) T
+ +create(data: CreateDto) T
+ +delete(id: string) void
+ }
+
+ class UserRepository {
+ +findByEmail(email: string) User
+ }
+
+ BaseRepository <|-- UserRepository
+```
+
+### 4. ER Diagram
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+erDiagram
+ User ||--o{ Session : has
+ User ||--o{ UserRole : has
+ Role ||--o{ UserRole : has
+
+ User {
+ string id PK
+ string email UK
+ string passwordHash
+ datetime createdAt
+ }
+
+ Session {
+ string id PK
+ string userId FK
+ string token UK
+ datetime expiresAt
+ }
+```
+
+### 5. System Architecture (Graph)
+
+```mermaid
+graph TD
+ Client[Web Client] --> Gateway[Traefik]
+ Gateway --> Auth[Auth Service]
+ Gateway --> User[User Service]
+
+ Auth --> DB[(PostgreSQL)]
+ User --> DB
+ Auth --> Cache[(Redis)]
+
+ style Gateway fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
+ style DB fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
+```
+
+---
+
+## Dark Color Palette / Bảng Màu Tối
+
+**LUÔN sử dụng bảng màu tối cho consistency:**
+
+| Màu | Hex | Sử dụng cho | Border |
+|-----|-----|-------------|--------|
+| **Primary** | `#2C3E50` | Start, main nodes | `#34495E` |
+| **Data** | `#34495E` | Database, Cache | `#2C3E50` |
+| **Success** | `#27AE60` | End, confirmation | `#229954` |
+| **Warning** | `#E67E22` | Decision nodes | `#D35400` |
+| **Error** | `#C0392B` | Error, failure | `#A93226` |
+| **Process** | `#8E44AD` | Processing steps | `#7D3C98` |
+| **Info** | `#3498DB` | Gateway, API | `#2980B9` |
+| **Neutral** | `#7F8C8D` | Neutral nodes | `#5D6D7E` |
+
+**Màu chữ:** `#ECF0F1` (light text for contrast)
+
+### Style Templates
+
+```markdown
+
+style Start fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
+style End fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:3px
+
+
+style Decision fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+
+
+style Error fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
+
+
+style Process fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+
+
+style DB fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
+
+
+style Gateway fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
+```
+
+---
+
+## Common Mistakes / Lỗi Thường Gặp
+
+### 1. Arrow Syntax
+
+```markdown
+❌ SAI: A -> B (single dash)
+✅ ĐÚNG: A --> B (double dash)
+```
+
+### 2. Missing Line Break
+
+```markdown
+❌ SAI: flowchart TD A --> B
+✅ ĐÚNG:
+flowchart TD
+ A --> B
+```
+
+### 3. Duplicate Node IDs
+
+```markdown
+❌ SAI:
+graph TD
+ A[Start]
+ A[Process]
+
+✅ ĐÚNG:
+graph TD
+ A[Start]
+ B[Process]
+```
+
+### 4. Style Syntax
+
+```markdown
+❌ SAI: style Node fill:#2C3E50
+✅ ĐÚNG: style Node fill:#2C3E50,color:#ECF0F1
+```
+
+### 5. Subgraph Format
+
+```markdown
+❌ SAI: subgraph "My Group"
+✅ ĐÚNG: subgraph MyGroup["My Group"]
+```
+
+### 6. Special Characters in Labels
+
+```markdown
+❌ SAI: A[Label (with parens)]
+✅ ĐÚNG: A["Label (with parens)"]
+```
+
+---
+
+## Visual Indicators / Emoji Indicators
+
+| Emoji | Meaning | Use in |
+|-------|---------|--------|
+| 🚀 | Start/Launch | Start nodes |
+| ✅ | Success | Success/End nodes |
+| ❌ | Error/Failed | Error nodes |
+| ⚠️ | Warning | Decision nodes |
+| 🔐 | Security/Auth | Auth steps |
+| 💾 | Database | Data nodes |
+| ⚙️ | Processing | Process nodes |
+| 🌐 | API/Network | External calls |
+
+**Example:**
+
+```mermaid
+flowchart TD
+ Start(["🚀 Bắt đầu"]) --> Auth{"🔐 Xác thực?"}
+ Auth -->|"Có"| Process["⚙️ Xử lý"]
+ Auth -->|"Không"| Error["❌ Lỗi"]
+ Process --> DB[("💾 Database")]
+ DB --> Success["✅ Thành công"]
+```
+
+---
+
+## Diagram Selection Matrix / Ma Trận Chọn Sơ đồ
+
+| Mục đích | Loại sơ đồ |
+|----------|------------|
+| Quy trình, workflow | **Flowchart** |
+| API calls, request/response | **Sequence** |
+| Code structure, OOP | **Class** |
+| System architecture | **Graph** |
+| Database schema | **ER Diagram** |
+| Project timeline | **Gantt** |
+| High-level context | **C4** |
+
+---
+
+## Testing Diagrams / Kiểm Tra
+
+```bash
+# Install mermaid-cli
+npm install -g @mermaid-js/mermaid-cli
+
+# Test render (SVG)
+mmdc -i your-doc.md -o test.svg
+
+# Render PNG với dark theme
+mmdc -i your-doc.md -o test.png -b black -t dark -s 3
+```
+
+---
+
+## Troubleshooting Checklist
+
+- [ ] Có xuống dòng sau `flowchart TD`?
+- [ ] Arrow sử dụng `-->` không phải `->`?
+- [ ] Không có Node ID trùng lặp?
+- [ ] Style có dấu phẩy giữa các thuộc tính?
+- [ ] Subgraph format: `subgraph ID["Label"]`?
+- [ ] Hex color có dấu `#`?
+- [ ] Labels đặc biệt được quote?
+
+---
+
+## Resources / Tài Nguyên
+
+- [Mermaid Official Documentation](https://mermaid.js.org/)
+- [Mermaid Live Editor](https://mermaid.live/)
+- [Mermaid CheatSheet](https://jojozhuang.github.io/tutorial/mermaid-cheat-sheet/)
+- [Documentation Skill](../documentation/SKILL.md) - Documentation guidelines
diff --git a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate
index 19b1c341..f5ab95d0 100644
Binary files a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate and b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/services/merchant-service-net/docs/en/README.md b/services/merchant-service-net/docs/en/README.md
index e1e32275..83b20815 100644
--- a/services/merchant-service-net/docs/en/README.md
+++ b/services/merchant-service-net/docs/en/README.md
@@ -116,13 +116,29 @@ dotnet ef database update \
| `POST` | `/api/v1/pos/devices/register` | Register POS device | ✅ Staff |
| `GET` | `/api/v1/pos/me` | Get current Staff info | ✅ Staff |
-### Admin Endpoints
+### Admin Merchant Endpoints (`/api/v1/admin/merchants`)
| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
-| `GET` | `/api/v1/admin/merchants` | List all merchants | ✅ Admin |
+| `GET` | `/api/v1/admin/merchants` | List all merchants (paginated) | ✅ Admin |
+| `GET` | `/api/v1/admin/merchants/statistics` | Merchant statistics | ✅ Admin |
+| `GET` | `/api/v1/admin/merchants/{id}` | Merchant details | ✅ Admin |
| `POST` | `/api/v1/admin/merchants/{id}/approve` | Approve merchant | ✅ Admin |
+| `POST` | `/api/v1/admin/merchants/{id}/reject` | Reject merchant | ✅ Admin |
| `POST` | `/api/v1/admin/merchants/{id}/suspend` | Suspend merchant | ✅ Admin |
+| `POST` | `/api/v1/admin/merchants/{id}/reactivate` | Reactivate merchant | ✅ Admin |
+| `POST` | `/api/v1/admin/merchants/{id}/ban` | Permanently ban merchant | ✅ Admin |
+
+### Admin Shop Endpoints (`/api/v1/admin/shops`)
+
+| Method | Endpoint | Description | Auth |
+|--------|----------|-------------|------|
+| `GET` | `/api/v1/admin/shops` | List all shops (paginated) | ✅ Admin |
+| `GET` | `/api/v1/admin/shops/{id}` | Shop details | ✅ Admin |
+| `POST` | `/api/v1/admin/shops/{id}/suspend` | Suspend shop | ✅ Admin |
+| `POST` | `/api/v1/admin/shops/{id}/reactivate` | Reactivate shop | ✅ Admin |
+| `POST` | `/api/v1/admin/shops/{id}/close` | Permanently close shop | ✅ Admin |
+
### Health Checks
diff --git a/services/merchant-service-net/docs/vi/README.md b/services/merchant-service-net/docs/vi/README.md
index b0ac5188..0d53640e 100644
--- a/services/merchant-service-net/docs/vi/README.md
+++ b/services/merchant-service-net/docs/vi/README.md
@@ -116,13 +116,29 @@ dotnet ef database update \
| `POST` | `/api/v1/pos/devices/register` | Đăng ký thiết bị POS | ✅ Staff |
| `GET` | `/api/v1/pos/me` | Thông tin Staff hiện tại | ✅ Staff |
-### Admin Endpoints
+### Admin Merchant Endpoints (`/api/v1/admin/merchants`)
| Method | Endpoint | Mô Tả | Auth |
|--------|----------|-------|------|
-| `GET` | `/api/v1/admin/merchants` | Danh sách tất cả Merchants | ✅ Admin |
+| `GET` | `/api/v1/admin/merchants` | Danh sách tất cả Merchants (phân trang) | ✅ Admin |
+| `GET` | `/api/v1/admin/merchants/statistics` | Thống kê Merchants | ✅ Admin |
+| `GET` | `/api/v1/admin/merchants/{id}` | Chi tiết Merchant | ✅ Admin |
| `POST` | `/api/v1/admin/merchants/{id}/approve` | Phê duyệt Merchant | ✅ Admin |
+| `POST` | `/api/v1/admin/merchants/{id}/reject` | Từ chối Merchant | ✅ Admin |
| `POST` | `/api/v1/admin/merchants/{id}/suspend` | Tạm ngưng Merchant | ✅ Admin |
+| `POST` | `/api/v1/admin/merchants/{id}/reactivate` | Kích hoạt lại Merchant | ✅ Admin |
+| `POST` | `/api/v1/admin/merchants/{id}/ban` | Cấm vĩnh viễn Merchant | ✅ Admin |
+
+### Admin Shop Endpoints (`/api/v1/admin/shops`)
+
+| Method | Endpoint | Mô Tả | Auth |
+|--------|----------|-------|------|
+| `GET` | `/api/v1/admin/shops` | Danh sách tất cả Shops (phân trang) | ✅ Admin |
+| `GET` | `/api/v1/admin/shops/{id}` | Chi tiết Shop | ✅ Admin |
+| `POST` | `/api/v1/admin/shops/{id}/suspend` | Tạm ngưng Shop | ✅ Admin |
+| `POST` | `/api/v1/admin/shops/{id}/reactivate` | Kích hoạt lại Shop | ✅ Admin |
+| `POST` | `/api/v1/admin/shops/{id}/close` | Đóng Shop vĩnh viễn | ✅ Admin |
+
### Health Checks
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Commands/Pos/PosCommands.cs b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Pos/PosCommands.cs
new file mode 100644
index 00000000..76167511
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Pos/PosCommands.cs
@@ -0,0 +1,148 @@
+// EN: POS commands for PIN auth and device registration.
+// VI: Commands POS cho xác thực PIN và đăng ký thiết bị.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.MerchantStaffAggregate;
+using MerchantService.Domain.Exceptions;
+
+namespace MerchantService.API.Application.Commands.Pos;
+
+#region PIN Auth Command
+
+///
+/// EN: Command to authenticate staff using PIN for POS.
+/// VI: Command để xác thực nhân viên sử dụng PIN cho POS.
+///
+public record PinAuthCommand : IRequest
+{
+ public Guid StaffId { get; init; }
+ public string PinCode { get; init; } = null!;
+ public string? DeviceId { get; init; }
+}
+
+public record PinAuthResult(
+ bool IsAuthenticated,
+ Guid? StaffId,
+ string? StaffEmail,
+ string? Role,
+ Guid? ShopId,
+ string? ErrorMessage);
+
+///
+/// EN: Handler for PinAuthCommand.
+/// VI: Handler cho PinAuthCommand.
+///
+public class PinAuthCommandHandler : IRequestHandler
+{
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly ILogger _logger;
+
+ public PinAuthCommandHandler(
+ IMerchantStaffRepository staffRepository,
+ ILogger logger)
+ {
+ _staffRepository = staffRepository;
+ _logger = logger;
+ }
+
+ public async Task Handle(PinAuthCommand request, CancellationToken cancellationToken)
+ {
+ var staff = await _staffRepository.GetByIdAsync(request.StaffId, cancellationToken);
+
+ if (staff == null)
+ {
+ return new PinAuthResult(false, null, null, null, null, "Staff not found");
+ }
+
+ if (staff.Status != StaffStatus.Active)
+ {
+ return new PinAuthResult(false, null, null, null, null, "Staff is not active");
+ }
+
+ if (!staff.VerifyPinCode(request.PinCode))
+ {
+ _logger.LogWarning("PIN verification failed for staff {StaffId}", request.StaffId);
+ return new PinAuthResult(false, null, null, null, null, "Invalid PIN");
+ }
+
+ // EN: Get first shop assignment if available
+ // VI: Lấy shop đầu tiên nếu có
+ var shopId = staff.ShopAssignments.FirstOrDefault()?.ShopId;
+
+ _logger.LogInformation("POS authentication successful for staff {StaffId}", staff.Id);
+
+ return new PinAuthResult(
+ true,
+ staff.Id,
+ staff.Email,
+ staff.Role.Name,
+ shopId,
+ null);
+ }
+}
+
+#endregion
+
+#region Register Device Command
+
+///
+/// EN: Command to register a POS device.
+/// VI: Command để đăng ký thiết bị POS.
+///
+public record RegisterDeviceCommand : IRequest
+{
+ public string DeviceId { get; init; } = null!;
+ public string DeviceName { get; init; } = null!;
+ public string Platform { get; init; } = null!;
+ public string? PushToken { get; init; }
+}
+
+public record RegisterDeviceResult(Guid StaffId, string DeviceId, bool IsRegistered);
+
+///
+/// EN: Handler for RegisterDeviceCommand.
+/// VI: Handler cho RegisterDeviceCommand.
+///
+public class RegisterDeviceCommandHandler : IRequestHandler
+{
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public RegisterDeviceCommandHandler(
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(RegisterDeviceCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var staff = await _staffRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Staff not found");
+
+ staff.RegisterDevice(request.DeviceId, request.DeviceName, request.PushToken, request.Platform);
+
+ _staffRepository.Update(staff);
+ await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Device registered: {DeviceId} for staff {StaffId}", request.DeviceId, staff.Id);
+
+ return new RegisterDeviceResult(staff.Id, request.DeviceId, true);
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/BranchCommands.cs b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/BranchCommands.cs
new file mode 100644
index 00000000..c809eb78
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/BranchCommands.cs
@@ -0,0 +1,179 @@
+// EN: Commands for branch management - Update and Delete.
+// VI: Commands cho quản lý chi nhánh - Cập nhật và Xóa.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.MerchantAggregate;
+using MerchantService.Domain.AggregatesModel.ShopAggregate;
+using MerchantService.Domain.Exceptions;
+
+namespace MerchantService.API.Application.Commands.Shops;
+
+#region Update Branch Command
+
+///
+/// EN: Command to update an existing branch.
+/// VI: Command để cập nhật chi nhánh.
+///
+public record UpdateBranchCommand : IRequest
+{
+ public Guid ShopId { get; init; }
+ public Guid BranchId { get; init; }
+ public string Name { get; init; } = null!;
+ public string? Code { get; init; }
+ public string Street { get; init; } = null!;
+ public string? Ward { get; init; }
+ public string District { get; init; } = null!;
+ public string City { get; init; } = null!;
+ public string? Province { get; init; }
+ public double? Latitude { get; init; }
+ public double? Longitude { get; init; }
+ public string? Phone { get; init; }
+}
+
+///
+/// EN: Handler for UpdateBranchCommand.
+/// VI: Handler cho UpdateBranchCommand.
+///
+public class UpdateBranchCommandHandler : IRequestHandler
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IShopRepository _shopRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public UpdateBranchCommandHandler(
+ IMerchantRepository merchantRepository,
+ IShopRepository shopRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _merchantRepository = merchantRepository;
+ _shopRepository = shopRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(UpdateBranchCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ var shop = await _shopRepository.GetByIdAsync(request.ShopId, cancellationToken)
+ ?? throw new DomainException("Shop not found");
+
+ if (shop.MerchantId != merchant.Id)
+ throw new DomainException("You do not have permission to update this shop");
+
+ var branch = shop.Branches.FirstOrDefault(b => b.Id == request.BranchId)
+ ?? throw new DomainException("Branch not found");
+
+ // EN: Build address using record with init
+ // VI: Xây dựng địa chỉ sử dụng record với init
+ var address = new Address
+ {
+ Street = request.Street,
+ Ward = request.Ward,
+ District = request.District,
+ City = request.City,
+ Province = request.Province
+ };
+
+ GeoLocation? location = null;
+ if (request.Latitude.HasValue && request.Longitude.HasValue)
+ {
+ location = new GeoLocation
+ {
+ Latitude = request.Latitude.Value,
+ Longitude = request.Longitude.Value
+ };
+ }
+
+ // EN: Update branch
+ // VI: Cập nhật chi nhánh
+ branch.Update(request.Name, request.Code, address, location);
+ if (!string.IsNullOrWhiteSpace(request.Phone))
+ branch.SetPhone(request.Phone);
+
+ _shopRepository.Update(shop);
+ await _shopRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Branch {BranchId} updated in Shop {ShopId}", request.BranchId, request.ShopId);
+ return true;
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
+
+#region Delete Branch Command
+
+///
+/// EN: Command to delete a branch.
+/// VI: Command để xóa chi nhánh.
+///
+public record DeleteBranchCommand(Guid ShopId, Guid BranchId) : IRequest;
+
+///
+/// EN: Handler for DeleteBranchCommand.
+/// VI: Handler cho DeleteBranchCommand.
+///
+public class DeleteBranchCommandHandler : IRequestHandler
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IShopRepository _shopRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public DeleteBranchCommandHandler(
+ IMerchantRepository merchantRepository,
+ IShopRepository shopRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _merchantRepository = merchantRepository;
+ _shopRepository = shopRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(DeleteBranchCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ var shop = await _shopRepository.GetByIdAsync(request.ShopId, cancellationToken)
+ ?? throw new DomainException("Shop not found");
+
+ if (shop.MerchantId != merchant.Id)
+ throw new DomainException("You do not have permission to modify this shop");
+
+ shop.RemoveBranch(request.BranchId);
+
+ _shopRepository.Update(shop);
+ await _shopRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Branch {BranchId} removed from Shop {ShopId}", request.BranchId, request.ShopId);
+ return true;
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/UpdateShopCommand.cs b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/UpdateShopCommand.cs
new file mode 100644
index 00000000..6532f4bb
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Shops/UpdateShopCommand.cs
@@ -0,0 +1,144 @@
+// EN: Command to update an existing shop.
+// VI: Command để cập nhật shop.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.MerchantAggregate;
+using MerchantService.Domain.AggregatesModel.ShopAggregate;
+using MerchantService.Domain.Exceptions;
+
+namespace MerchantService.API.Application.Commands.Shops;
+
+///
+/// EN: Command to update an existing shop.
+/// VI: Command để cập nhật shop.
+///
+public record UpdateShopCommand : IRequest
+{
+ ///
+ /// EN: Shop ID to update.
+ /// VI: ID shop cần cập nhật.
+ ///
+ public Guid ShopId { get; init; }
+
+ ///
+ /// EN: Shop name.
+ /// VI: Tên shop.
+ ///
+ public string Name { get; init; } = null!;
+
+ ///
+ /// EN: Shop description.
+ /// VI: Mô tả shop.
+ ///
+ public string? Description { get; init; }
+
+ ///
+ /// EN: Phone number.
+ /// VI: Số điện thoại.
+ ///
+ public string? Phone { get; init; }
+
+ ///
+ /// EN: Email address.
+ /// VI: Địa chỉ email.
+ ///
+ public string? Email { get; init; }
+
+ ///
+ /// EN: Website URL.
+ /// VI: URL website.
+ ///
+ public string? Website { get; init; }
+
+ ///
+ /// EN: Logo URL.
+ /// VI: URL logo.
+ ///
+ public string? LogoUrl { get; init; }
+
+ ///
+ /// EN: Cover image URL.
+ /// VI: URL ảnh bìa.
+ ///
+ public string? CoverImageUrl { get; init; }
+}
+
+///
+/// EN: Handler for UpdateShopCommand.
+/// VI: Handler cho UpdateShopCommand.
+///
+public class UpdateShopCommandHandler : IRequestHandler
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IShopRepository _shopRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public UpdateShopCommandHandler(
+ IMerchantRepository merchantRepository,
+ IShopRepository shopRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _merchantRepository = merchantRepository;
+ _shopRepository = shopRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(UpdateShopCommand request, CancellationToken cancellationToken)
+ {
+ // EN: Get current user ID from claims
+ // VI: Lấy user ID hiện tại từ claims
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ {
+ throw new DomainException("User not authenticated");
+ }
+
+ // EN: Get merchant by user ID
+ // VI: Lấy merchant theo user ID
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ // EN: Get and validate shop ownership
+ // VI: Lấy và kiểm tra quyền sở hữu shop
+ var shop = await _shopRepository.GetByIdAsync(request.ShopId, cancellationToken)
+ ?? throw new DomainException("Shop not found");
+
+ if (shop.MerchantId != merchant.Id)
+ {
+ throw new DomainException("You do not have permission to update this shop");
+ }
+
+ // EN: Update shop information
+ // VI: Cập nhật thông tin shop
+ shop.UpdateInfo(request.Name, request.Description);
+
+ // EN: Update contact info
+ // VI: Cập nhật thông tin liên hệ
+ shop.UpdateContactInfo(new ContactInfo
+ {
+ Phone = request.Phone ?? string.Empty,
+ Email = request.Email,
+ Website = request.Website
+ });
+
+ // EN: Update images
+ // VI: Cập nhật hình ảnh
+ shop.UpdateImages(request.LogoUrl, request.CoverImageUrl);
+
+ // EN: Save changes
+ // VI: Lưu thay đổi
+ _shopRepository.Update(shop);
+ await _shopRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Shop updated: {ShopId} by merchant {MerchantId}",
+ shop.Id, merchant.Id);
+
+ return true;
+ }
+}
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Commands/Staff/StaffCommands.cs b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Staff/StaffCommands.cs
new file mode 100644
index 00000000..5aa35bf9
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Commands/Staff/StaffCommands.cs
@@ -0,0 +1,315 @@
+// EN: Staff management commands.
+// VI: Commands quản lý nhân viên.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.MerchantAggregate;
+using MerchantService.Domain.AggregatesModel.MerchantStaffAggregate;
+using MerchantService.Domain.Exceptions;
+
+namespace MerchantService.API.Application.Commands.Staff;
+
+#region Invite Staff Command
+
+///
+/// EN: Command to invite a new staff member.
+/// VI: Command để mời nhân viên mới.
+///
+public record InviteStaffCommand : IRequest
+{
+ public string Email { get; init; } = null!;
+ public string Role { get; init; } = "Cashier";
+ public Guid? ShopId { get; init; }
+ public Guid? BranchId { get; init; }
+}
+
+public record InviteStaffResult(Guid StaffId, string Email, string Status);
+
+///
+/// EN: Handler for InviteStaffCommand.
+/// VI: Handler cho InviteStaffCommand.
+///
+public class InviteStaffCommandHandler : IRequestHandler
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public InviteStaffCommandHandler(
+ IMerchantRepository merchantRepository,
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _merchantRepository = merchantRepository;
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(InviteStaffCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ // EN: Parse role
+ // VI: Parse vai trò
+ var role = request.Role.ToLowerInvariant() switch
+ {
+ "cashier" => StaffRole.Cashier,
+ "waiter" => StaffRole.Waiter,
+ "manager" => StaffRole.Manager,
+ "admin" => StaffRole.Admin,
+ _ => StaffRole.Cashier
+ };
+
+ // EN: Create invitation
+ // VI: Tạo lời mời
+ var staff = MerchantStaff.Invite(
+ merchant.Id,
+ request.Email,
+ role,
+ StaffPermissions.ViewSales | StaffPermissions.ProcessPayment);
+
+ _staffRepository.Add(staff);
+ await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Staff invited: {StaffId} to merchant {MerchantId}", staff.Id, merchant.Id);
+
+ return new InviteStaffResult(staff.Id, staff.Email ?? request.Email, staff.Status.Name);
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
+
+#region Update Staff Command
+
+///
+/// EN: Command to update a staff member.
+/// VI: Command để cập nhật nhân viên.
+///
+public record UpdateStaffCommand : IRequest
+{
+ public Guid StaffId { get; init; }
+ public string? EmployeeCode { get; init; }
+ public string? Phone { get; init; }
+ public string? Role { get; init; }
+}
+
+///
+/// EN: Handler for UpdateStaffCommand.
+/// VI: Handler cho UpdateStaffCommand.
+///
+public class UpdateStaffCommandHandler : IRequestHandler
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public UpdateStaffCommandHandler(
+ IMerchantRepository merchantRepository,
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _merchantRepository = merchantRepository;
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(UpdateStaffCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ var staff = await _staffRepository.GetByIdAsync(request.StaffId, cancellationToken)
+ ?? throw new DomainException("Staff not found");
+
+ if (staff.MerchantId != merchant.Id)
+ throw new DomainException("You do not have permission to update this staff");
+
+ staff.Update(request.EmployeeCode, request.Phone);
+
+ if (!string.IsNullOrEmpty(request.Role))
+ {
+ var role = request.Role.ToLowerInvariant() switch
+ {
+ "cashier" => StaffRole.Cashier,
+ "waiter" => StaffRole.Waiter,
+ "manager" => StaffRole.Manager,
+ "admin" => StaffRole.Admin,
+ _ => null
+ };
+ if (role != null)
+ staff.UpdateRole(role);
+ }
+
+ _staffRepository.Update(staff);
+ await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Staff updated: {StaffId}", request.StaffId);
+ return true;
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
+
+#region Delete Staff Command
+
+///
+/// EN: Command to delete/terminate a staff member.
+/// VI: Command để xóa/chấm dứt nhân viên.
+///
+public record DeleteStaffCommand(Guid StaffId) : IRequest;
+
+///
+/// EN: Handler for DeleteStaffCommand.
+/// VI: Handler cho DeleteStaffCommand.
+///
+public class DeleteStaffCommandHandler : IRequestHandler
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public DeleteStaffCommandHandler(
+ IMerchantRepository merchantRepository,
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _merchantRepository = merchantRepository;
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(DeleteStaffCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ var staff = await _staffRepository.GetByIdAsync(request.StaffId, cancellationToken)
+ ?? throw new DomainException("Staff not found");
+
+ if (staff.MerchantId != merchant.Id)
+ throw new DomainException("You do not have permission to delete this staff");
+
+ staff.Terminate();
+
+ _staffRepository.Update(staff);
+ await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Staff terminated: {StaffId}", request.StaffId);
+ return true;
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
+
+#region Accept Invite Command
+
+///
+/// EN: Command to accept a staff invitation.
+/// VI: Command để chấp nhận lời mời làm nhân viên.
+///
+public record AcceptInviteCommand : IRequest
+{
+ public string InvitationToken { get; init; } = null!;
+ public string PinCode { get; init; } = null!;
+}
+
+public record AcceptInviteResult(Guid StaffId, string Status);
+
+///
+/// EN: Handler for AcceptInviteCommand.
+/// VI: Handler cho AcceptInviteCommand.
+///
+public class AcceptInviteCommandHandler : IRequestHandler
+{
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly ILogger _logger;
+
+ public AcceptInviteCommandHandler(
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor,
+ ILogger logger)
+ {
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ _logger = logger;
+ }
+
+ public async Task Handle(AcceptInviteCommand request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+
+ // EN: Find staff by invitation token (simplified - in real app use secure token)
+ // VI: Tìm nhân viên theo token mời (đơn giản - app thực tế dùng token bảo mật)
+ if (!Guid.TryParse(request.InvitationToken, out var staffId))
+ throw new DomainException("Invalid invitation token");
+
+ var staff = await _staffRepository.GetByIdAsync(staffId, cancellationToken)
+ ?? throw new DomainException("Invitation not found");
+
+ if (staff.Status != StaffStatus.Invited)
+ throw new DomainException("Invitation is no longer valid");
+
+ // EN: Accept invitation and set PIN
+ // VI: Chấp nhận lời mời và đặt PIN
+ staff.AcceptInvitation(userId);
+ staff.SetPinCode(request.PinCode);
+
+ _staffRepository.Update(staff);
+ await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ _logger.LogInformation("Staff invitation accepted: {StaffId} by user {UserId}", staffId, userId);
+
+ return new AcceptInviteResult(staff.Id, staff.Status.Name);
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Queries/Pos/PosQueries.cs b/services/merchant-service-net/src/MerchantService.API/Application/Queries/Pos/PosQueries.cs
new file mode 100644
index 00000000..bb70cf1a
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Queries/Pos/PosQueries.cs
@@ -0,0 +1,118 @@
+// EN: POS queries for staff information.
+// VI: Queries POS cho thông tin nhân viên.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.MerchantStaffAggregate;
+using MerchantService.Domain.Exceptions;
+
+namespace MerchantService.API.Application.Queries.Pos;
+
+#region Get POS Staff Query
+
+///
+/// EN: Query to get current staff info for POS.
+/// VI: Query để lấy thông tin nhân viên hiện tại cho POS.
+///
+public record GetPosStaffQuery : IRequest;
+
+///
+/// EN: Handler for GetPosStaffQuery.
+/// VI: Handler cho GetPosStaffQuery.
+///
+public class GetPosStaffQueryHandler : IRequestHandler
+{
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public GetPosStaffQueryHandler(
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ public async Task Handle(GetPosStaffQuery request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ if (userId == null) return null;
+
+ var staff = await _staffRepository.GetByUserIdAsync(userId.Value, cancellationToken);
+ if (staff == null) return null;
+
+ return new PosStaffDto
+ {
+ StaffId = staff.Id,
+ MerchantId = staff.MerchantId,
+ Email = staff.Email ?? string.Empty,
+ EmployeeCode = staff.EmployeeCode,
+ Phone = staff.Phone,
+ Role = staff.Role.Name,
+ Status = staff.Status.Name,
+ Permissions = (int)staff.Permissions,
+ HasPinCode = !string.IsNullOrEmpty(staff.PinCodeHash),
+ ShopAssignments = staff.ShopAssignments.Select(sm => new PosShopAssignmentDto
+ {
+ ShopId = sm.ShopId,
+ ShopRole = sm.Role.Name,
+ BranchId = sm.BranchId
+ }).ToList(),
+ RegisteredDevices = staff.DeviceTokens.Select(d => new PosDeviceDto
+ {
+ DeviceId = d.DeviceId,
+ DeviceName = d.DeviceName ?? string.Empty,
+ Platform = d.Platform,
+ LastUsedAt = d.LastUsedAt
+ }).ToList()
+ };
+ }
+
+ private Guid? GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ return null;
+ return userId;
+ }
+}
+
+#endregion
+
+#region POS DTOs
+
+///
+/// EN: POS staff DTO.
+/// VI: DTO nhân viên POS.
+///
+public record PosStaffDto
+{
+ public Guid StaffId { get; init; }
+ public Guid MerchantId { get; init; }
+ public string Email { get; init; } = null!;
+ public string? EmployeeCode { get; init; }
+ public string? Phone { get; init; }
+ public string Role { get; init; } = null!;
+ public string Status { get; init; } = null!;
+ public int Permissions { get; init; }
+ public bool HasPinCode { get; init; }
+ public List ShopAssignments { get; init; } = [];
+ public List RegisteredDevices { get; init; } = [];
+}
+
+public record PosShopAssignmentDto
+{
+ public Guid ShopId { get; init; }
+ public string ShopRole { get; init; } = null!;
+ public Guid? BranchId { get; init; }
+}
+
+public record PosDeviceDto
+{
+ public string DeviceId { get; init; } = null!;
+ public string DeviceName { get; init; } = null!;
+ public string Platform { get; init; } = null!;
+ public DateTime? LastUsedAt { get; init; }
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Queries/Shops/BranchQueries.cs b/services/merchant-service-net/src/MerchantService.API/Application/Queries/Shops/BranchQueries.cs
new file mode 100644
index 00000000..4b5d931b
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Queries/Shops/BranchQueries.cs
@@ -0,0 +1,175 @@
+// EN: Queries for branch management.
+// VI: Queries cho quản lý chi nhánh.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.ShopAggregate;
+
+namespace MerchantService.API.Application.Queries.Shops;
+
+#region Get Branches Query
+
+///
+/// EN: Query to get all branches of a shop.
+/// VI: Query để lấy tất cả chi nhánh của shop.
+///
+public record GetBranchesQuery(Guid ShopId) : IRequest>;
+
+///
+/// EN: Handler for GetBranchesQuery.
+/// VI: Handler cho GetBranchesQuery.
+///
+public class GetBranchesQueryHandler : IRequestHandler>
+{
+ private readonly IShopRepository _shopRepository;
+
+ public GetBranchesQueryHandler(IShopRepository shopRepository)
+ {
+ _shopRepository = shopRepository;
+ }
+
+ public async Task> Handle(GetBranchesQuery request, CancellationToken cancellationToken)
+ {
+ var shop = await _shopRepository.GetByIdAsync(request.ShopId, cancellationToken);
+ if (shop == null)
+ return [];
+
+ return shop.Branches.Select(b => new ShopBranchDto
+ {
+ Id = b.Id,
+ Name = b.Name,
+ Code = b.Code,
+ Address = new AddressDto
+ {
+ Street = b.Address.Street,
+ Ward = b.Address.Ward,
+ District = b.Address.District,
+ City = b.Address.City,
+ Province = b.Address.Province,
+ FullAddress = b.Address.FullAddress
+ },
+ Location = b.Location != null ? new GeoLocationDto
+ {
+ Latitude = b.Location.Latitude,
+ Longitude = b.Location.Longitude
+ } : null,
+ Phone = b.Phone,
+ IsActive = b.IsActive
+ }).ToList();
+ }
+}
+
+#endregion
+
+#region Get Nearby Shops Query
+
+///
+/// EN: Query to find shops with branches near a location.
+/// VI: Query để tìm shops có chi nhánh gần vị trí.
+///
+public record GetNearbyShopsQuery(
+ double Latitude,
+ double Longitude,
+ double RadiusKm = 5.0,
+ int Limit = 20) : IRequest>;
+
+///
+/// EN: DTO for nearby shop result.
+/// VI: DTO cho kết quả tìm shop gần đây.
+///
+public record NearbyShopDto
+{
+ public Guid ShopId { get; init; }
+ public string Name { get; init; } = null!;
+ public string Slug { get; init; } = null!;
+ public string Category { get; init; } = null!;
+ public string? LogoUrl { get; init; }
+ public Guid BranchId { get; init; }
+ public string BranchName { get; init; } = null!;
+ public AddressDto Address { get; init; } = null!;
+ public double DistanceKm { get; init; }
+}
+
+///
+/// EN: Handler for GetNearbyShopsQuery.
+/// VI: Handler cho GetNearbyShopsQuery.
+///
+public class GetNearbyShopsQueryHandler : IRequestHandler>
+{
+ private readonly IShopRepository _shopRepository;
+
+ public GetNearbyShopsQueryHandler(IShopRepository shopRepository)
+ {
+ _shopRepository = shopRepository;
+ }
+
+ public async Task> Handle(GetNearbyShopsQuery request, CancellationToken cancellationToken)
+ {
+ // EN: Get all active shops with their branches
+ // VI: Lấy tất cả shops active với các chi nhánh
+ var shops = await _shopRepository.GetActiveShopsWithBranchesAsync(cancellationToken);
+
+ var results = new List();
+
+ foreach (var shop in shops)
+ {
+ foreach (var branch in shop.Branches.Where(b => b.IsActive && b.Location != null))
+ {
+ var distance = CalculateDistance(
+ request.Latitude, request.Longitude,
+ branch.Location!.Latitude, branch.Location.Longitude);
+
+ if (distance <= request.RadiusKm)
+ {
+ results.Add(new NearbyShopDto
+ {
+ ShopId = shop.Id,
+ Name = shop.Name,
+ Slug = shop.Slug,
+ Category = shop.Category.Name,
+ LogoUrl = shop.LogoUrl,
+ BranchId = branch.Id,
+ BranchName = branch.Name,
+ Address = new AddressDto
+ {
+ Street = branch.Address.Street,
+ Ward = branch.Address.Ward,
+ District = branch.Address.District,
+ City = branch.Address.City,
+ Province = branch.Address.Province,
+ FullAddress = branch.Address.FullAddress
+ },
+ DistanceKm = Math.Round(distance, 2)
+ });
+ }
+ }
+ }
+
+ return results
+ .OrderBy(r => r.DistanceKm)
+ .Take(request.Limit)
+ .ToList();
+ }
+
+ ///
+ /// EN: Calculate distance between two coordinates using Haversine formula.
+ /// VI: Tính khoảng cách giữa hai tọa độ sử dụng công thức Haversine.
+ ///
+ private static double CalculateDistance(double lat1, double lon1, double lat2, double lon2)
+ {
+ const double R = 6371; // Earth's radius in km
+
+ var dLat = ToRadians(lat2 - lat1);
+ var dLon = ToRadians(lon2 - lon1);
+
+ var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
+ Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
+ Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
+
+ var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
+ return R * c;
+ }
+
+ private static double ToRadians(double degrees) => degrees * Math.PI / 180;
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Application/Queries/Staff/StaffQueries.cs b/services/merchant-service-net/src/MerchantService.API/Application/Queries/Staff/StaffQueries.cs
new file mode 100644
index 00000000..f11fc74b
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Application/Queries/Staff/StaffQueries.cs
@@ -0,0 +1,112 @@
+// EN: Staff queries.
+// VI: Queries cho nhân viên.
+
+using MediatR;
+using MerchantService.Domain.AggregatesModel.MerchantAggregate;
+using MerchantService.Domain.AggregatesModel.MerchantStaffAggregate;
+using MerchantService.Domain.Exceptions;
+
+namespace MerchantService.API.Application.Queries.Staff;
+
+#region Get My Staff Query
+
+///
+/// EN: Query to get current merchant's staff list.
+/// VI: Query để lấy danh sách nhân viên của merchant hiện tại.
+///
+public record GetMyStaffQuery : IRequest>;
+
+///
+/// EN: Handler for GetMyStaffQuery.
+/// VI: Handler cho GetMyStaffQuery.
+///
+public class GetMyStaffQueryHandler : IRequestHandler>
+{
+ private readonly IMerchantRepository _merchantRepository;
+ private readonly IMerchantStaffRepository _staffRepository;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public GetMyStaffQueryHandler(
+ IMerchantRepository merchantRepository,
+ IMerchantStaffRepository staffRepository,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _merchantRepository = merchantRepository;
+ _staffRepository = staffRepository;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ public async Task> Handle(GetMyStaffQuery request, CancellationToken cancellationToken)
+ {
+ var userId = GetUserId();
+ var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
+ ?? throw new DomainException("Merchant not found");
+
+ var staffList = await _staffRepository.GetByMerchantIdAsync(merchant.Id, cancellationToken);
+
+ return staffList.Select(s => new StaffDto
+ {
+ Id = s.Id,
+ Email = s.Email ?? string.Empty,
+ EmployeeCode = s.EmployeeCode,
+ Phone = s.Phone,
+ Role = s.Role.Name,
+ Status = s.Status.Name,
+ Permissions = (int)s.Permissions,
+ HasPinCode = !string.IsNullOrEmpty(s.PinCodeHash),
+ CreatedAt = s.CreatedAt,
+ UpdatedAt = s.UpdatedAt,
+ ShopAssignments = s.ShopAssignments.Select(sm => new ShopAssignmentDto
+ {
+ ShopId = sm.ShopId,
+ ShopRole = sm.Role.Name,
+ BranchId = sm.BranchId
+ }).ToList()
+ }).ToList();
+ }
+
+ private Guid GetUserId()
+ {
+ var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
+ ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
+ throw new DomainException("User not authenticated");
+ return userId;
+ }
+}
+
+#endregion
+
+#region Staff DTOs
+
+///
+/// EN: Staff DTO.
+/// VI: DTO nhân viên.
+///
+public record StaffDto
+{
+ public Guid Id { get; init; }
+ public string Email { get; init; } = null!;
+ public string? EmployeeCode { get; init; }
+ public string? Phone { get; init; }
+ public string Role { get; init; } = null!;
+ public string Status { get; init; } = null!;
+ public int Permissions { get; init; }
+ public bool HasPinCode { get; init; }
+ public DateTime CreatedAt { get; init; }
+ public DateTime? UpdatedAt { get; init; }
+ public List ShopAssignments { get; init; } = [];
+}
+
+///
+/// EN: Shop assignment DTO.
+/// VI: DTO gán shop.
+///
+public record ShopAssignmentDto
+{
+ public Guid ShopId { get; init; }
+ public string ShopRole { get; init; } = null!;
+ public Guid? BranchId { get; init; }
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Controllers/MerchantsController.cs b/services/merchant-service-net/src/MerchantService.API/Controllers/MerchantsController.cs
index 1fa7cd4a..650525c2 100644
--- a/services/merchant-service-net/src/MerchantService.API/Controllers/MerchantsController.cs
+++ b/services/merchant-service-net/src/MerchantService.API/Controllers/MerchantsController.cs
@@ -14,7 +14,7 @@ namespace MerchantService.API.Controllers;
/// VI: Controller để quản lý merchant.
///
[ApiController]
-[Route("api/[controller]")]
+[Route("api/v1/merchants")]
[Authorize]
public class MerchantsController : ControllerBase
{
@@ -31,7 +31,7 @@ public class MerchantsController : ControllerBase
/// EN: Get current merchant's profile.
/// VI: Lấy profile của merchant hiện tại.
///
- [HttpGet("profile")]
+ [HttpGet("me")]
[ProducesResponseType(typeof(MerchantProfileDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task GetProfile()
@@ -70,7 +70,7 @@ public class MerchantsController : ControllerBase
/// EN: Update merchant information.
/// VI: Cập nhật thông tin merchant.
///
- [HttpPut("profile")]
+ [HttpPut("me")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task UpdateProfile([FromBody] UpdateMerchantCommand command)
@@ -83,7 +83,7 @@ public class MerchantsController : ControllerBase
/// EN: Submit for verification.
/// VI: Nộp hồ sơ xác minh.
///
- [HttpPost("verification/submit")]
+ [HttpPost("me/verify")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task SubmitVerification()
diff --git a/services/merchant-service-net/src/MerchantService.API/Controllers/PosController.cs b/services/merchant-service-net/src/MerchantService.API/Controllers/PosController.cs
new file mode 100644
index 00000000..cb3eaec5
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Controllers/PosController.cs
@@ -0,0 +1,122 @@
+// EN: POS Controller for Point of Sale operations.
+// VI: Controller POS cho các thao tác bán hàng.
+
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using MerchantService.API.Application.Commands.Pos;
+using MerchantService.API.Application.Queries.Pos;
+
+namespace MerchantService.API.Controllers;
+
+///
+/// EN: Controller for POS (Point of Sale) operations.
+/// VI: Controller cho các thao tác POS (Point of Sale).
+///
+[ApiController]
+[Route("api/v1/pos")]
+public class PosController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public PosController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator;
+ _logger = logger;
+ }
+
+ ///
+ /// EN: Authenticate staff using PIN for POS operations.
+ /// VI: Xác thực nhân viên sử dụng PIN cho hoạt động POS.
+ ///
+ [HttpPost("auth/pin")]
+ [AllowAnonymous]
+ [ProducesResponseType(typeof(PinAuthResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task PinAuth([FromBody] PinAuthRequest request)
+ {
+ var command = new PinAuthCommand
+ {
+ StaffId = request.StaffId,
+ PinCode = request.PinCode,
+ DeviceId = request.DeviceId
+ };
+
+ var result = await _mediator.Send(command);
+
+ if (!result.IsAuthenticated)
+ {
+ return Unauthorized(new { message = result.ErrorMessage });
+ }
+
+ return Ok(result);
+ }
+
+ ///
+ /// EN: Register a device for POS/push notifications.
+ /// VI: Đăng ký thiết bị cho POS/push notifications.
+ ///
+ [HttpPost("devices/register")]
+ [Authorize]
+ [ProducesResponseType(typeof(RegisterDeviceResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task RegisterDevice([FromBody] RegisterDeviceRequest request)
+ {
+ try
+ {
+ var command = new RegisterDeviceCommand
+ {
+ DeviceId = request.DeviceId,
+ DeviceName = request.DeviceName,
+ Platform = request.Platform,
+ PushToken = request.PushToken
+ };
+
+ var result = await _mediator.Send(command);
+ _logger.LogInformation("Device registered: {DeviceId}", request.DeviceId);
+ return Ok(result);
+ }
+ catch (Domain.Exceptions.DomainException ex)
+ {
+ return BadRequest(new { message = ex.Message });
+ }
+ }
+
+ ///
+ /// EN: Get current staff information for POS.
+ /// VI: Lấy thông tin nhân viên hiện tại cho POS.
+ ///
+ [HttpGet("me")]
+ [Authorize]
+ [ProducesResponseType(typeof(PosStaffDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task GetMe()
+ {
+ var result = await _mediator.Send(new GetPosStaffQuery());
+ if (result == null)
+ {
+ return NotFound(new { message = "Staff not found" });
+ }
+ return Ok(result);
+ }
+}
+
+#region Request Models
+
+public record PinAuthRequest
+{
+ public Guid StaffId { get; init; }
+ public string PinCode { get; init; } = null!;
+ public string? DeviceId { get; init; }
+}
+
+public record RegisterDeviceRequest
+{
+ public string DeviceId { get; init; } = null!;
+ public string DeviceName { get; init; } = null!;
+ public string Platform { get; init; } = null!;
+ public string? PushToken { get; init; }
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.API/Controllers/ShopsController.cs b/services/merchant-service-net/src/MerchantService.API/Controllers/ShopsController.cs
index 6d3fa97f..b72494af 100644
--- a/services/merchant-service-net/src/MerchantService.API/Controllers/ShopsController.cs
+++ b/services/merchant-service-net/src/MerchantService.API/Controllers/ShopsController.cs
@@ -14,7 +14,7 @@ namespace MerchantService.API.Controllers;
/// VI: Controller để quản lý shop.
///
[ApiController]
-[Route("api/[controller]")]
+[Route("api/v1/shops")]
[Authorize]
public class ShopsController : ControllerBase
{
@@ -31,7 +31,7 @@ public class ShopsController : ControllerBase
/// EN: Get current merchant's shops.
/// VI: Lấy danh sách shops của merchant hiện tại.
///
- [HttpGet("my-shops")]
+ [HttpGet]
[ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)]
public async Task GetMyShops()
{
@@ -97,6 +97,43 @@ public class ShopsController : ControllerBase
}
}
+ ///
+ /// EN: Update an existing shop.
+ /// VI: Cập nhật shop.
+ ///
+ [HttpPut("{shopId:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task Update(Guid shopId, [FromBody] UpdateShopRequest request)
+ {
+ try
+ {
+ var command = new UpdateShopCommand
+ {
+ ShopId = shopId,
+ Name = request.Name,
+ Description = request.Description,
+ Phone = request.Phone,
+ Email = request.Email,
+ Website = request.Website,
+ LogoUrl = request.LogoUrl,
+ CoverImageUrl = request.CoverImageUrl
+ };
+ await _mediator.Send(command);
+ _logger.LogInformation("Shop updated: {ShopId}", shopId);
+ return Ok(new { message = "Shop updated successfully" });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("not found"))
+ {
+ return NotFound(new { message = ex.Message });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("permission"))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = ex.Message });
+ }
+ }
+
///
/// EN: Publish a shop (make visible to customers).
/// VI: Công khai shop (hiển thị với khách hàng).
@@ -161,6 +198,91 @@ public class ShopsController : ControllerBase
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { shopId }, result);
}
+
+ ///
+ /// EN: Get all branches of a shop.
+ /// VI: Lấy tất cả chi nhánh của shop.
+ ///
+ [HttpGet("{shopId:guid}/branches")]
+ [AllowAnonymous]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task GetBranches(Guid shopId)
+ {
+ var result = await _mediator.Send(new GetBranchesQuery(shopId));
+ return Ok(result);
+ }
+
+ ///
+ /// EN: Update a branch.
+ /// VI: Cập nhật chi nhánh.
+ ///
+ [HttpPut("{shopId:guid}/branches/{branchId:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task UpdateBranch(Guid shopId, Guid branchId, [FromBody] UpdateBranchRequest request)
+ {
+ try
+ {
+ var command = new UpdateBranchCommand
+ {
+ ShopId = shopId,
+ BranchId = branchId,
+ Name = request.Name,
+ Code = request.Code,
+ Street = request.Street,
+ Ward = request.Ward,
+ District = request.District,
+ City = request.City,
+ Province = request.Province,
+ Latitude = request.Latitude,
+ Longitude = request.Longitude,
+ Phone = request.Phone
+ };
+ await _mediator.Send(command);
+ return Ok(new { message = "Branch updated successfully" });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("not found"))
+ {
+ return NotFound(new { message = ex.Message });
+ }
+ }
+
+ ///
+ /// EN: Delete a branch.
+ /// VI: Xóa chi nhánh.
+ ///
+ [HttpDelete("{shopId:guid}/branches/{branchId:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeleteBranch(Guid shopId, Guid branchId)
+ {
+ try
+ {
+ await _mediator.Send(new DeleteBranchCommand(shopId, branchId));
+ return Ok(new { message = "Branch deleted successfully" });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("not found"))
+ {
+ return NotFound(new { message = ex.Message });
+ }
+ }
+
+ ///
+ /// EN: Find shops with branches near a location.
+ /// VI: Tìm shops có chi nhánh gần vị trí.
+ ///
+ [HttpGet("nearby")]
+ [AllowAnonymous]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task GetNearbyShops(
+ [FromQuery] double lat,
+ [FromQuery] double lng,
+ [FromQuery] double radius = 5.0,
+ [FromQuery] int limit = 20)
+ {
+ var result = await _mediator.Send(new GetNearbyShopsQuery(lat, lng, radius, limit));
+ return Ok(result);
+ }
}
///
@@ -180,3 +302,37 @@ public record AddBranchRequest
public double? Longitude { get; init; }
public string? Phone { get; init; }
}
+
+///
+/// EN: Request model for updating a shop.
+/// VI: Model request để cập nhật shop.
+///
+public record UpdateShopRequest
+{
+ public string Name { get; init; } = null!;
+ public string? Description { get; init; }
+ public string? Phone { get; init; }
+ public string? Email { get; init; }
+ public string? Website { get; init; }
+ public string? LogoUrl { get; init; }
+ public string? CoverImageUrl { get; init; }
+}
+
+///
+/// EN: Request model for updating a branch.
+/// VI: Model request để cập nhật chi nhánh.
+///
+public record UpdateBranchRequest
+{
+ public string Name { get; init; } = null!;
+ public string? Code { get; init; }
+ public string Street { get; init; } = null!;
+ public string? Ward { get; init; }
+ public string District { get; init; } = null!;
+ public string City { get; init; } = null!;
+ public string? Province { get; init; }
+ public double? Latitude { get; init; }
+ public double? Longitude { get; init; }
+ public string? Phone { get; init; }
+}
+
diff --git a/services/merchant-service-net/src/MerchantService.API/Controllers/StaffController.cs b/services/merchant-service-net/src/MerchantService.API/Controllers/StaffController.cs
new file mode 100644
index 00000000..16eeb907
--- /dev/null
+++ b/services/merchant-service-net/src/MerchantService.API/Controllers/StaffController.cs
@@ -0,0 +1,194 @@
+// EN: Staff Controller for staff management.
+// VI: Controller Staff để quản lý nhân viên.
+
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using MerchantService.API.Application.Commands.Staff;
+using MerchantService.API.Application.Queries.Staff;
+
+namespace MerchantService.API.Controllers;
+
+///
+/// EN: Controller for merchant staff management.
+/// VI: Controller để quản lý nhân viên của merchant.
+///
+[ApiController]
+[Route("api/v1/merchants/me/staff")]
+[Authorize]
+public class StaffController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public StaffController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator;
+ _logger = logger;
+ }
+
+ ///
+ /// EN: Get current merchant's staff list.
+ /// VI: Lấy danh sách nhân viên của merchant hiện tại.
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)]
+ public async Task GetMyStaff()
+ {
+ var result = await _mediator.Send(new GetMyStaffQuery());
+ return Ok(result);
+ }
+
+ ///
+ /// EN: Invite a new staff member.
+ /// VI: Mời nhân viên mới.
+ ///
+ [HttpPost("invite")]
+ [ProducesResponseType(typeof(InviteStaffResult), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task InviteStaff([FromBody] InviteStaffRequest request)
+ {
+ try
+ {
+ var command = new InviteStaffCommand
+ {
+ Email = request.Email,
+ Role = request.Role,
+ ShopId = request.ShopId,
+ BranchId = request.BranchId
+ };
+ var result = await _mediator.Send(command);
+ _logger.LogInformation("Staff invited: {Email}", request.Email);
+ return CreatedAtAction(nameof(GetMyStaff), result);
+ }
+ catch (Domain.Exceptions.DomainException ex)
+ {
+ return BadRequest(new { message = ex.Message });
+ }
+ }
+
+ ///
+ /// EN: Update a staff member.
+ /// VI: Cập nhật nhân viên.
+ ///
+ [HttpPut("{staffId:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task UpdateStaff(Guid staffId, [FromBody] UpdateStaffRequest request)
+ {
+ try
+ {
+ var command = new UpdateStaffCommand
+ {
+ StaffId = staffId,
+ EmployeeCode = request.EmployeeCode,
+ Phone = request.Phone,
+ Role = request.Role
+ };
+ await _mediator.Send(command);
+ return Ok(new { message = "Staff updated successfully" });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("not found"))
+ {
+ return NotFound(new { message = ex.Message });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("permission"))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = ex.Message });
+ }
+ }
+
+ ///
+ /// EN: Delete/terminate a staff member.
+ /// VI: Xóa/chấm dứt nhân viên.
+ ///
+ [HttpDelete("{staffId:guid}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeleteStaff(Guid staffId)
+ {
+ try
+ {
+ await _mediator.Send(new DeleteStaffCommand(staffId));
+ return Ok(new { message = "Staff terminated successfully" });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("not found"))
+ {
+ return NotFound(new { message = ex.Message });
+ }
+ catch (Domain.Exceptions.DomainException ex) when (ex.Message.Contains("permission"))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = ex.Message });
+ }
+ }
+}
+
+///
+/// EN: Controller for staff public endpoints.
+/// VI: Controller cho các endpoint công khai của staff.
+///
+[ApiController]
+[Route("api/v1/staff")]
+[Authorize]
+public class StaffPublicController : ControllerBase
+{
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
+
+ public StaffPublicController(IMediator mediator, ILogger logger)
+ {
+ _mediator = mediator;
+ _logger = logger;
+ }
+
+ ///
+ /// EN: Accept a staff invitation.
+ /// VI: Chấp nhận lời mời làm nhân viên.
+ ///
+ [HttpPost("accept-invite")]
+ [ProducesResponseType(typeof(AcceptInviteResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task AcceptInvite([FromBody] AcceptInviteRequest request)
+ {
+ try
+ {
+ var command = new AcceptInviteCommand
+ {
+ InvitationToken = request.InvitationToken,
+ PinCode = request.PinCode
+ };
+ var result = await _mediator.Send(command);
+ _logger.LogInformation("Staff invitation accepted: {StaffId}", result.StaffId);
+ return Ok(result);
+ }
+ catch (Domain.Exceptions.DomainException ex)
+ {
+ return BadRequest(new { message = ex.Message });
+ }
+ }
+}
+
+#region Request Models
+
+public record InviteStaffRequest
+{
+ public string Email { get; init; } = null!;
+ public string Role { get; init; } = "Cashier";
+ public Guid? ShopId { get; init; }
+ public Guid? BranchId { get; init; }
+}
+
+public record UpdateStaffRequest
+{
+ public string? EmployeeCode { get; init; }
+ public string? Phone { get; init; }
+ public string? Role { get; init; }
+}
+
+public record AcceptInviteRequest
+{
+ public string InvitationToken { get; init; } = null!;
+ public string PinCode { get; init; } = null!;
+}
+
+#endregion
diff --git a/services/merchant-service-net/src/MerchantService.Domain/AggregatesModel/ShopAggregate/IShopRepository.cs b/services/merchant-service-net/src/MerchantService.Domain/AggregatesModel/ShopAggregate/IShopRepository.cs
index 1602dcec..84ff1a17 100644
--- a/services/merchant-service-net/src/MerchantService.Domain/AggregatesModel/ShopAggregate/IShopRepository.cs
+++ b/services/merchant-service-net/src/MerchantService.Domain/AggregatesModel/ShopAggregate/IShopRepository.cs
@@ -58,4 +58,10 @@ public interface IShopRepository : IRepository
/// VI: Cập nhật shop.
///
void Update(Shop shop);
+
+ ///
+ /// EN: Get all active shops with branches loaded for nearby search.
+ /// VI: Lấy tất cả shops active kèm chi nhánh để tìm kiếm gần đây.
+ ///
+ Task> GetActiveShopsWithBranchesAsync(CancellationToken cancellationToken = default);
}
diff --git a/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs b/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs
index 73898a64..3af9cc68 100644
--- a/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs
+++ b/services/merchant-service-net/src/MerchantService.Infrastructure/Repositories/ShopRepository.cs
@@ -82,4 +82,13 @@ public class ShopRepository : IShopRepository
{
_context.Entry(shop).State = EntityState.Modified;
}
+
+ ///
+ public async Task> GetActiveShopsWithBranchesAsync(CancellationToken cancellationToken = default)
+ {
+ return await _context.Shops
+ .Include(s => s.Branches)
+ .Where(s => !s.IsDeleted && s.StatusId == ShopStatus.Active.Id)
+ .ToListAsync(cancellationToken);
+ }
}
diff --git a/services/mining-service-net/docs/en/ARCHITECTURE.md b/services/mining-service-net/docs/en/ARCHITECTURE.md
new file mode 100644
index 00000000..225ff6f2
--- /dev/null
+++ b/services/mining-service-net/docs/en/ARCHITECTURE.md
@@ -0,0 +1,466 @@
+# Mining Service Architecture
+
+## Overview
+
+The Mining Service provides a gamified point mining system inspired by Pi Network, enabling users to accumulate Mining Points through daily engagement and community building.
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+graph TD
+ subgraph API["🌐 API Layer"]
+ Controllers[Controllers]
+ Commands[Commands]
+ Queries[Queries]
+ end
+
+ subgraph Domain["⚙️ Domain Layer"]
+ Miner[Miner Aggregate]
+ Circle[Circle Aggregate]
+ Referral[Referral Aggregate]
+ end
+
+ subgraph Infra["💾 Infrastructure Layer"]
+ EF[EF Core]
+ Redis[(Redis Cache)]
+ RabbitMQ[RabbitMQ]
+ end
+
+ subgraph Data["🗄️ Data Storage"]
+ DB[(PostgreSQL)]
+ end
+
+ API --> Domain
+ Domain --> Infra
+ EF --> DB
+
+ style API fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
+ style Domain fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+ style Infra fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
+ style Data fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
+```
+
+## Architecture Patterns
+
+### Domain-Driven Design (DDD)
+
+- **Aggregates**: Miner, Circle, Referral
+- **Entities**: MiningSession, MiningHistory, CircleMember
+- **Value Objects**: MiningRate, MiningPoints, MiningSession
+- **Domain Events**: MiningSessionStarted, PointsMined, CircleCompleted, ReferralActivated
+
+### CQRS Pattern
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ subgraph Write["📝 Commands"]
+ C1[StartMiningCommand]
+ C2[ClaimRewardCommand]
+ C3[CreateCircleCommand]
+ C4[InviteToCircleCommand]
+ C5[ApplyReferralCommand]
+ end
+
+ subgraph Read["📖 Queries"]
+ Q1[GetMinerStatusQuery]
+ Q2[GetMiningHistoryQuery]
+ Q3[GetCircleQuery]
+ Q4[GetReferralsQuery]
+ Q5[GetLeaderboardQuery]
+ end
+
+ style Write fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style Read fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+```
+
+## Domain Model
+
+### Miner Aggregate
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class Miner {
+ +Guid Id
+ +Guid UserId
+ +MinerRole Role
+ +decimal TotalMinedPoints
+ +MiningRate CurrentRate
+ +MiningSession ActiveSession
+ +string ReferralCode
+ +Guid ReferredBy
+ +MinerStatus Status
+ +StartMiningSession()
+ +ClaimMiningReward()
+ +UpgradeRole()
+ +RecalculateMiningRate()
+ }
+
+ class MiningSession {
+ +Guid SessionId
+ +DateTime StartTime
+ +DateTime EndTime
+ +decimal AccumulatedPoints
+ +SessionStatus Status
+ }
+
+ class MiningRate {
+ +decimal BaseRate
+ +decimal CircleBonus
+ +decimal ReferralBonus
+ +decimal RoleBonus
+ +decimal TotalRate
+ }
+
+ class MiningHistory {
+ +Guid Id
+ +decimal PointsEarned
+ +string Source
+ +DateTime EarnedAt
+ }
+
+ Miner "1" --> "0..1" MiningSession : has
+ Miner --> MiningRate : uses
+ Miner "1" --> "*" MiningHistory : tracks
+```
+
+### Circle Aggregate
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class Circle {
+ +Guid Id
+ +Guid OwnerId
+ +string Name
+ +List~CircleMember~ Members
+ +decimal TrustScore
+ +decimal BonusMultiplier
+ +CircleStatus Status
+ +AddMember()
+ +RemoveMember()
+ +CalculateTrustScore()
+ +Validate()
+ }
+
+ class CircleMember {
+ +Guid Id
+ +Guid MinerId
+ +DateTime JoinedAt
+ +bool IsActive
+ }
+
+ Circle "1" --> "3..5" CircleMember : contains
+```
+
+### Referral Aggregate
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class Referral {
+ +Guid Id
+ +Guid ReferrerId
+ +Guid ReferredId
+ +string ReferralCode
+ +decimal BonusRate
+ +bool IsActive
+ +int Level
+ +DateTime CreatedAt
+ +Activate()
+ +Deactivate()
+ +CalculateBonus()
+ }
+```
+
+## Mining Rate Formula
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ Base["🎯 Base Rate
0.25 MP/hour"] --> Multiply1((×))
+ Role["👤 Role Bonus
+0-50%"] --> Multiply1
+ Multiply1 --> Multiply2((×))
+ Circle["🔵 Circle Bonus
+25%"] --> Multiply2
+ Multiply2 --> Multiply3((×))
+ Referral["👥 Referral Bonus
+25%/each"] --> Multiply3
+ Multiply3 --> Total["✅ Total Rate
MP/hour"]
+
+ style Base fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:2px
+ style Role fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+ style Circle fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+ style Referral fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style Total fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:3px
+```
+
+**Example Calculation:**
+| Component | Value | Multiplier |
+|-----------|-------|------------|
+| Base Rate | 0.25 MP/hour | - |
+| Role (Ambassador) | +25% | × 1.25 |
+| Valid Circle | +25% | × 1.25 |
+| 2 Referrals | +50% | × 1.50 |
+| **Total** | **0.585 MP/hour** | **14.04 MP/day** |
+
+## Database Schema
+
+### ER Diagram
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+erDiagram
+ Miner ||--o{ MiningSession : has
+ Miner ||--o{ MiningHistory : tracks
+ Miner ||--o| Circle : owns
+ Circle ||--|{ CircleMember : contains
+ Miner ||--o{ Referral : refers
+ Miner ||--o| Referral : referredBy
+
+ Miner {
+ uuid Id PK
+ uuid UserId UK
+ string Role
+ decimal TotalMinedPoints
+ string ReferralCode UK
+ uuid ReferredBy FK
+ string Status
+ datetime CreatedAt
+ }
+
+ MiningSession {
+ uuid Id PK
+ uuid MinerId FK
+ datetime StartTime
+ datetime EndTime
+ decimal AccumulatedPoints
+ string Status
+ }
+
+ MiningHistory {
+ uuid Id PK
+ uuid MinerId FK
+ decimal PointsEarned
+ string Source
+ datetime EarnedAt
+ }
+
+ Circle {
+ uuid Id PK
+ uuid OwnerId FK
+ string Name
+ decimal TrustScore
+ decimal BonusMultiplier
+ string Status
+ }
+
+ CircleMember {
+ uuid Id PK
+ uuid CircleId FK
+ uuid MinerId FK
+ datetime JoinedAt
+ bool IsActive
+ }
+
+ Referral {
+ uuid Id PK
+ uuid ReferrerId FK
+ uuid ReferredId FK
+ string ReferralCode
+ decimal BonusRate
+ bool IsActive
+ int Level
+ }
+```
+
+### Key Indexes
+
+| Index | Columns | Purpose |
+|-------|---------|---------|
+| `IX_Miners_UserId` | UserId | Fast lookup by user |
+| `IX_Miners_ReferralCode` | ReferralCode | Referral code lookup |
+| `IX_MiningSessions_MinerId_Status` | MinerId, Status | Active session check |
+| `IX_Referrals_ReferrerId` | ReferrerId | Referral list |
+
+## API Flow
+
+### Start Mining Session
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+sequenceDiagram
+ participant Client as 📱 Client
+ participant API as 🌐 Mining API
+ participant Handler as ⚙️ CommandHandler
+ participant Miner as 👤 MinerAggregate
+ participant Calc as 🧮 RateCalculator
+ participant DB as 💾 PostgreSQL
+ participant Cache as ⚡ Redis
+
+ Client->>API: POST /api/v1/mining/start
+ API->>Handler: StartMiningCommand
+ Handler->>Miner: ValidateNoActiveSession()
+ Handler->>Calc: CalculateMiningRate()
+ Calc-->>Handler: MiningRate
+ Handler->>Miner: CreateSession(24h)
+ Miner->>DB: SaveSession()
+ Miner->>Cache: CacheSessionInfo(ttl: 24h)
+ API-->>Client: 200 OK { session_id, rate, end_time }
+```
+
+### Claim Mining Reward
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+sequenceDiagram
+ participant Client as 📱 Client
+ participant API as 🌐 Mining API
+ participant Handler as ⚙️ CommandHandler
+ participant Miner as 👤 MinerAggregate
+ participant DB as 💾 PostgreSQL
+ participant MQ as 📨 RabbitMQ
+
+ Client->>API: POST /api/v1/mining/claim
+ API->>Handler: ClaimRewardCommand
+ Handler->>Miner: GetActiveSession()
+
+ alt Session Completed
+ Miner->>Miner: CalculateEarnedPoints()
+ Miner->>Miner: AddToTotalPoints()
+ Miner->>DB: SaveMiningHistory()
+ Miner->>MQ: Publish PointsMinedEvent
+ API-->>Client: 200 OK { earned_points, total_points }
+ else Session Not Ready
+ API-->>Client: 400 Bad Request
+ end
+```
+
+## Inter-Service Communication
+
+### Service Dependencies
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+graph TD
+ subgraph External["🔐 Authentication"]
+ IAM[IAM Service]
+ end
+
+ subgraph Core["⛏️ Mining Service"]
+ Mining[Mining Service]
+ end
+
+ subgraph Integration["🔗 Integrations"]
+ Social[Social Service]
+ Wallet[Wallet Service]
+ end
+
+ IAM -->|JWT Validation| Mining
+ Social <-->|Friend Data| Mining
+ Mining -->|Point Conversion| Wallet
+
+ style External fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
+ style Core fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:3px
+ style Integration fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+```
+
+### Integration Events (RabbitMQ)
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ subgraph Publishers["📤 Publishers"]
+ IAM[IAM Service]
+ Social[Social Service]
+ Mining1[Mining Service]
+ end
+
+ subgraph Events["📨 Events"]
+ E1[UserRegisteredEvent]
+ E2[FriendAddedEvent]
+ E3[PointsMinedEvent]
+ end
+
+ subgraph Consumers["📥 Consumers"]
+ Mining2[Mining Service]
+ Wallet[Wallet Service]
+ end
+
+ IAM --> E1 --> Mining2
+ Social --> E2 --> Mining2
+ Mining1 --> E3 --> Wallet
+
+ style Events fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+```
+
+## Deployment
+
+### Docker Compose
+
+```yaml
+mining-service:
+ build:
+ context: ../..
+ dockerfile: services/mining-service-net/Dockerfile
+ environment:
+ - DATABASE_URL=${MINING_DATABASE_URL}
+ - REDIS_URL=${REDIS_URL}
+ - RABBITMQ_URL=${RABBITMQ_URL}
+ - JWT_AUTHORITY=${IAM_SERVICE_URL}
+ labels:
+ - traefik.http.routers.mining.rule=PathPrefix(`/api/v1/mining`)
+```
+
+### Health Checks
+
+| Endpoint | Check |
+|----------|-------|
+| `/health/live` | ✅ Service running |
+| `/health/ready` | ✅ DB + Redis connected |
+| `/health` | ✅ Full status |
+
+## Security
+
+### Rate Limiting
+- 1 mining session start per 24 hours
+- 10 circle invites per day
+
+### Anti-Fraud
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart TD
+ Request([🚀 Request]) --> Device{🔍 Device Check}
+ Device -->|New Device| Flag[⚠️ Flag for Review]
+ Device -->|Known| IP{🌐 IP Check}
+ IP -->|Suspicious| Block[❌ Block]
+ IP -->|Normal| KYC{🔐 KYC Verified?}
+ KYC -->|No| Limited[⚠️ Limited Features]
+ KYC -->|Yes| Full[✅ Full Access]
+
+ style Request fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
+ style Block fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
+ style Full fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
+ style Device fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style IP fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style KYC fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+```
+
+## Caching Strategy (Redis)
+
+| Key Pattern | TTL | Purpose |
+|-------------|-----|---------|
+| `session:{minerId}` | 24h | Active session cache |
+| `rate:{minerId}` | 1h | Mining rate cache |
+| `leaderboard:daily` | 5m | Leaderboard cache |
+
+## Monitoring
+
+### Metrics
+- Mining sessions started/day
+- Points mined total
+- Active miners count
+- Referral conversion rate
+
+### Logging
+- Serilog structured logging
+- Correlation IDs for tracing
+- Prometheus + Grafana integration
diff --git a/services/mining-service-net/docs/en/README.md b/services/mining-service-net/docs/en/README.md
new file mode 100644
index 00000000..de13c8ae
--- /dev/null
+++ b/services/mining-service-net/docs/en/README.md
@@ -0,0 +1,515 @@
+# Mining Service .NET
+
+> **EN**: Mining Point management service with Pi Network-inspired mechanism for GoodGo Platform.
+> **VI**: Dịch vụ quản lý Mining Point với cơ chế lấy cảm hứng từ Pi Network cho GoodGo Platform.
+
+## Overview
+
+The Mining Service provides a **gamified loyalty point mining system** inspired by Pi Network, allowing users to accumulate Mining Points (MP) through daily engagement, social referrals, and community building activities.
+
+### Key Features
+
+| Feature | Description |
+|---------|-------------|
+| **Daily Mining** | Tap-to-mine mechanism - users activate daily mining sessions |
+| **Mining Rate** | Base rate increases through referrals and circle building |
+| **🔥 Streak Bonus** | TikTok-style consecutive daily mining rewards |
+| **Security Circles** | Trusted groups that boost mining rate and network security |
+| **Referral System** | Multi-level referral bonuses for network growth |
+| **User Roles** | Pioneer, Contributor, Ambassador, Node Operator tiers |
+| **Point Conversion** | Convert Mining Points to platform loyalty points |
+
+---
+
+## Architecture Design
+
+### System Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ API Gateway (Traefik) │
+└────────────────────────────────┬────────────────────────────────────┘
+ │
+┌────────────────────────────────▼────────────────────────────────────┐
+│ Mining Service .NET │
+├─────────────────────────────────────────────────────────────────────┤
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Mining │ │ Circle │ │ Referral │ │ Rate │ │
+│ │ Session │ │ Manager │ │ Tracker │ │ Calculator │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────────────────┤
+│ Domain Layer │
+│ ┌─────────────────────┐ ┌─────────────────────┐ │
+│ │ MinerAggregate │ │ CircleAggregate │ │
+│ │ - MiningSession │ │ - CircleMember │ │
+│ │ - MiningHistory │ │ - TrustLevel │ │
+│ │ - MiningRate │ │ - CircleBonus │ │
+│ └─────────────────────┘ └─────────────────────┘ │
+├─────────────────────────────────────────────────────────────────────┤
+│ Infrastructure Layer │
+│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
+│ │ PostgreSQL │ │ Redis │ │ RabbitMQ │ │
+│ │ (EF Core) │ │ (Cache) │ │ (Events) │ │
+│ └──────────────┘ └──────────────┘ └──────────────┘ │
+└─────────────────────────────────────────────────────────────────────┘
+ │
+ ┌────────────────────────┼────────────────────────┐
+ ▼ ▼ ▼
+┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ IAM Service │ │ Wallet Service │ │ Social Service │
+│ (Auth/Users) │ │ (Point Convert) │ │ (Friends) │
+└───────────────┘ └─────────────────┘ └─────────────────┘
+```
+
+### Clean Architecture Structure
+
+```
+mining-service-net/
+├── src/
+│ ├── MiningService.API/ # API Layer
+│ │ ├── Controllers/
+│ │ │ ├── MiningController.cs # Mining session APIs
+│ │ │ ├── CirclesController.cs # Security circle APIs
+│ │ │ └── ReferralsController.cs # Referral APIs
+│ │ └── Application/
+│ │ ├── Commands/ # Write operations
+│ │ └── Queries/ # Read operations
+│ │
+│ ├── MiningService.Domain/ # Domain Layer
+│ │ ├── AggregatesModel/
+│ │ │ ├── MinerAggregate/ # User mining profile
+│ │ │ ├── CircleAggregate/ # Security circles
+│ │ │ └── ReferralAggregate/ # Referral tracking
+│ │ ├── Events/ # Domain events
+│ │ ├── Exceptions/ # Domain exceptions
+│ │ └── Services/ # Domain services
+│ │
+│ └── MiningService.Infrastructure/ # Infrastructure Layer
+│ ├── EntityConfigurations/ # EF Core mappings
+│ ├── Repositories/ # Data access
+│ └── MiningServiceContext.cs # DbContext
+│
+├── tests/
+│ ├── MiningService.UnitTests/
+│ └── MiningService.FunctionalTests/
+│
+├── docs/
+│ ├── en/
+│ └── vi/
+│
+└── Dockerfile
+```
+
+---
+
+## Domain Model
+
+### Core Aggregates
+
+#### 1. Miner Aggregate (User Mining Profile)
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ Miner (Entity) │
+├────────────────────────────────────────────────────────────────────┤
+│ Properties: │
+│ - Id: Guid │
+│ - UserId: Guid (from IAM Service) │
+│ - Role: MinerRole (Pioneer/Contributor/Ambassador/NodeOperator) │
+│ - TotalMinedPoints: decimal │
+│ - CurrentMiningRate: MiningRate (Value Object) │
+│ - CurrentSession: MiningSession? │
+│ - SecurityCircle: Circle? │
+│ - ReferralCode: string │
+│ - ReferredBy: Guid? │
+│ - Status: MinerStatus (Active/Suspended/Banned) │
+│ - CreatedAt: DateTime │
+├────────────────────────────────────────────────────────────────────┤
+│ Behaviors: │
+│ - StartMiningSession() → MiningSession │
+│ - ClaimMiningReward() → MiningPoints │
+│ - UpgradeRole(role) → void │
+│ - JoinCircle(circle) → void │
+│ - RecalculateMiningRate() → MiningRate │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+#### 2. Circle Aggregate (Security Circle)
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ Circle (Entity) │
+├────────────────────────────────────────────────────────────────────┤
+│ Properties: │
+│ - Id: Guid │
+│ - OwnerId: Guid (Miner who created the circle) │
+│ - Members: List (max 5) │
+│ - Name: string │
+│ - TrustScore: decimal (0-100) │
+│ - BonusMultiplier: decimal │
+│ - Status: CircleStatus (Active/Incomplete/Disbanded) │
+│ - CreatedAt: DateTime │
+├────────────────────────────────────────────────────────────────────┤
+│ Behaviors: │
+│ - AddMember(miner) → void │
+│ - RemoveMember(minerId) → void │
+│ - CalculateTrustScore() → decimal │
+│ - CalculateBonusMultiplier() → decimal │
+│ - Validate() → bool (min 3 members required) │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+#### 3. Referral Aggregate
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ Referral (Entity) │
+├────────────────────────────────────────────────────────────────────┤
+│ Properties: │
+│ - Id: Guid │
+│ - ReferrerId: Guid (who invited) │
+│ - ReferredId: Guid (who was invited) │
+│ - ReferralCode: string │
+│ - BonusRate: decimal │
+│ - IsActive: bool │
+│ - Level: int (1 = direct, 2 = indirect) │
+│ - CreatedAt: DateTime │
+├────────────────────────────────────────────────────────────────────┤
+│ Behaviors: │
+│ - Activate() → void │
+│ - Deactivate() → void │
+│ - CalculateBonus(baseRate) → decimal │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+### Value Objects
+
+```csharp
+/// Mining Rate calculation
+public record MiningRate(
+ decimal BaseRate, // Default: 0.25 MP/hour
+ decimal CircleBonus, // +0.25x for valid circle
+ decimal ReferralBonus, // +25% per active referral
+ decimal RoleBonus, // Based on role tier
+ decimal TotalRate // Combined rate
+);
+
+/// Mining Session tracking
+public record MiningSession(
+ Guid SessionId,
+ DateTime StartTime,
+ DateTime EndTime, // StartTime + 24 hours
+ decimal AccumulatedPoints,
+ MiningSessionStatus Status // Active/Completed/Expired
+);
+
+/// Mining Points
+public record MiningPoints(
+ decimal Amount,
+ DateTime EarnedAt,
+ string Source // Mining/Referral/CircleBonus/RoleBonus
+);
+```
+
+---
+
+## Mining Mechanism
+
+### 🔥 Streak Bonus System (TikTok-style)
+
+Consecutive daily mining rewards users with escalating bonuses:
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ subgraph Streak["🔥 Streak Tiers"]
+ D1["Day 1-2
+0%"] --> D3["Day 3-6
+10%"]
+ D3 --> D7["Day 7-13
+25%"]
+ D7 --> D14["Day 14-29
+50%"]
+ D14 --> D30["Day 30+
+100%"]
+ end
+
+ style D1 fill:#7F8C8D,color:#ECF0F1,stroke:#5D6D7E,stroke-width:2px
+ style D3 fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+ style D7 fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+ style D14 fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style D30 fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:3px
+```
+
+#### Streak Mechanics
+
+| Streak Days | Bonus | Milestone Reward |
+|-------------|-------|------------------|
+| Day 1-2 | +0% | - |
+| Day 3-6 | +10% | 🎁 3-day badge |
+| Day 7-13 | +25% | 🎁 7-day badge + 50 MP bonus |
+| Day 14-29 | +50% | 🎁 14-day badge + 100 MP bonus |
+| Day 30-59 | +100% | 🔥 30-day badge + 300 MP bonus |
+| Day 60-89 | +125% | 🏆 60-day badge + 500 MP bonus |
+| Day 90+ | +150% | 👑 90-day badge + 1000 MP bonus |
+
+#### Streak Protection Rules
+
+- **Grace Period**: Miss 1 day → streak pauses (not reset)
+- **Streak Freeze**: Use 1 Freeze Token to protect streak (earn 1 token per 7-day streak)
+- **Streak Recovery**: Within 24h of missing → pay 50 MP to restore streak
+- **Maximum Streak**: Unlimited (displayed on leaderboard)
+
+#### Streak Value Object
+
+```csharp
+/// Streak tracking for consecutive mining
+public record MiningStreak(
+ int CurrentStreak, // Current consecutive days
+ int LongestStreak, // Personal best
+ DateTime LastMiningDate, // Last successful claim
+ int FreezeTokens, // Available streak protections
+ bool IsGracePeriod, // Currently in grace period
+ decimal BonusMultiplier // Current streak bonus
+);
+```
+
+---
+
+### Mining Rate Formula
+
+```
+Total Mining Rate = Base Rate × (1 + Role) × (1 + Circle) × (1 + Referral) × (1 + Streak)
+
+Where:
+- Base Rate: 0.25 MP/hour (configurable)
+- Role Multiplier: Pioneer=0%, Contributor=10%, Ambassador=25%, Node=50%
+- Circle Bonus: 25% if valid circle (3-5 trusted members)
+- Referral Bonus: 25% per active direct referral (capped at 100%)
+- Streak Bonus: 0-150% based on consecutive daily mining
+```
+
+**Example with Streak:**
+| Component | Value | Multiplier |
+|-----------|-------|------------|
+| Base Rate | 0.25 MP/hour | - |
+| Role (Ambassador) | +25% | × 1.25 |
+| Valid Circle | +25% | × 1.25 |
+| 2 Referrals | +50% | × 1.50 |
+| 30-Day Streak | +100% | × 2.00 |
+| **Total** | **1.17 MP/hour** | **28.12 MP/day** |
+
+### Mining Session Flow
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+sequenceDiagram
+ participant U as 📱 User
+ participant A as 🌐 Mining API
+ participant M as ⚙️ MinerAggregate
+ participant R as 🧮 RateCalculator
+ participant DB as 💾 PostgreSQL
+ participant C as ⚡ Redis Cache
+
+ U->>A: POST /api/v1/mining/start
+ A->>M: StartMiningSession()
+ M->>R: CalculateMiningRate()
+ R-->>M: MiningRate
+ M->>M: CreateSession(24h duration)
+ M->>DB: SaveSession()
+ M->>C: CacheSessionInfo()
+ A-->>U: 200 OK { session_id, rate, end_time }
+
+ Note over U: 24 hours later...
+
+ U->>A: POST /api/v1/mining/claim
+ A->>M: ClaimMiningReward()
+ M->>M: CalculateEarnedPoints()
+ M->>M: AddToTotalPoints()
+ M->>DB: SaveMiningHistory()
+ A-->>U: 200 OK { earned_points, total_points }
+```
+
+### User Roles & Benefits
+
+| Role | Requirements | Mining Bonus | Benefits |
+|------|--------------|--------------|----------|
+| **Pioneer** | Sign up | 0% | Basic mining |
+| **Contributor** | Valid security circle (3+ members) | +10% | Circle bonus active |
+| **Ambassador** | 5+ active referrals | +25% | Referral bonus cap increased |
+| **Node Operator** | Run node software (future) | +50% | Network rewards |
+
+---
+
+## API Endpoints
+
+### Mining APIs
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| `GET` | `/api/v1/mining/me` | Get current mining status |
+| `POST` | `/api/v1/mining/start` | Start 24-hour mining session |
+| `POST` | `/api/v1/mining/claim` | Claim mining rewards |
+| `GET` | `/api/v1/mining/history` | Get mining history |
+| `GET` | `/api/v1/mining/rate` | Get current mining rate breakdown |
+| `GET` | `/api/v1/mining/leaderboard` | Get top miners leaderboard |
+
+### Security Circle APIs
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| `GET` | `/api/v1/circles/me` | Get my security circle |
+| `POST` | `/api/v1/circles` | Create security circle |
+| `POST` | `/api/v1/circles/invite` | Invite member to circle |
+| `POST` | `/api/v1/circles/accept/{inviteId}` | Accept circle invitation |
+| `DELETE` | `/api/v1/circles/members/{memberId}` | Remove circle member |
+| `GET` | `/api/v1/circles/trust-score` | Get circle trust score |
+
+### Referral APIs
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| `GET` | `/api/v1/referrals/code` | Get my referral code |
+| `GET` | `/api/v1/referrals` | List my referrals |
+| `GET` | `/api/v1/referrals/stats` | Get referral statistics |
+| `POST` | `/api/v1/referrals/apply` | Apply referral code (during signup) |
+
+### Admin APIs
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| `GET` | `/api/v1/admin/miners` | List all miners (paginated) |
+| `PUT` | `/api/v1/admin/miners/{id}/suspend` | Suspend miner |
+| `PUT` | `/api/v1/admin/miners/{id}/ban` | Ban miner |
+| `PUT` | `/api/v1/admin/config/mining-rate` | Update base mining rate |
+
+---
+
+## Integration Points
+
+### Service Dependencies
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ Mining Service │
+├─────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌──────────────┐ Authenticates ┌──────────────────────┐ │
+│ │ │◄────────────────────────│ IAM Service │ │
+│ │ │ │ - User validation │ │
+│ │ │ │ - JWT tokens │ │
+│ │ │ └──────────────────────┘ │
+│ │ │ │
+│ │ Mining │ Point Conversion ┌──────────────────────┐ │
+│ │ Service │────────────────────────►│ Wallet Service │ │
+│ │ │ │ - Convert MP to LP │ │
+│ │ │ │ - Transaction │ │
+│ │ │ └──────────────────────┘ │
+│ │ │ │
+│ │ │ Social Graph ┌──────────────────────┐ │
+│ │ │◄───────────────────────►│ Social Service │ │
+│ │ │ │ - Friend list │ │
+│ │ │ │ - Trust validation │ │
+│ └──────────────┘ └──────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────┘
+```
+
+### Integration Events (RabbitMQ)
+
+| Event | Publisher | Consumer(s) | Description |
+|-------|-----------|-------------|-------------|
+| `MinerCreatedEvent` | Mining Service | IAM Service | New miner profile created |
+| `MiningSessionStartedEvent` | Mining Service | - | Mining session started |
+| `PointsMinedEvent` | Mining Service | Wallet Service | Points claimed, sync to wallet |
+| `CircleCompletedEvent` | Mining Service | - | Security circle became valid |
+| `ReferralActivatedEvent` | Mining Service | - | Referral became active |
+| `UserRegisteredEvent` | IAM Service | Mining Service | Create miner profile |
+| `FriendAddedEvent` | Social Service | Mining Service | Update circle suggestions |
+
+---
+
+## Tech Stack
+
+| Component | Technology |
+|-----------|------------|
+| Framework | .NET 10 |
+| Database | PostgreSQL (EF Core) |
+| Caching | Redis (StackExchange.Redis) |
+| Message Queue | RabbitMQ (MassTransit) |
+| CQRS | MediatR |
+| Validation | FluentValidation |
+| API Docs | Swagger/OpenAPI |
+| Logging | Serilog |
+| Observability | Prometheus + Grafana |
+
+---
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Description | Required | Default |
+|----------|-------------|----------|---------|
+| `DATABASE_URL` | PostgreSQL connection | Yes | - |
+| `REDIS_URL` | Redis connection | Yes | - |
+| `RABBITMQ_URL` | RabbitMQ connection | Yes | - |
+| `JWT_AUTHORITY` | JWT issuer URL | Yes | - |
+| `MINING_BASE_RATE` | Base mining rate (MP/hour) | No | 0.25 |
+| `MINING_SESSION_HOURS` | Session duration | No | 24 |
+| `CIRCLE_MIN_MEMBERS` | Minimum circle members | No | 3 |
+| `CIRCLE_MAX_MEMBERS` | Maximum circle members | No | 5 |
+| `REFERRAL_BONUS_PERCENT` | Bonus per referral | No | 25 |
+| `REFERRAL_BONUS_CAP` | Max referral bonus | No | 100 |
+
+---
+
+## Security Considerations
+
+### Anti-Fraud Measures
+
+1. **Rate Limiting** - Max 1 mining session start per 24 hours
+2. **Device Fingerprint** - Track device changes, flag suspicious activity
+3. **IP Monitoring** - Detect multiple accounts from same IP
+4. **Circle Validation** - Members must be real, active users
+5. **Referral Verification** - Referrals must pass KYC to activate bonus
+6. **Activity Scoring** - Penalize inactive or bot-like patterns
+
+### Data Protection
+
+- Mining history encrypted at rest
+- Personal data follows GDPR compliance
+- Audit logs for all admin actions
+
+---
+
+## Roadmap
+
+### Phase 1: Core Mining (MVP)
+- [x] Miner profile creation
+- [x] Daily tap-to-mine sessions
+- [x] Basic mining rate calculation
+- [x] Mining history
+
+### Phase 2: Social Features
+- [ ] Security circles
+- [ ] Referral system
+- [ ] Circle bonus calculation
+
+### Phase 3: Advanced Features
+- [ ] User role progression
+- [ ] Point conversion to wallet
+- [ ] Leaderboards
+
+### Phase 4: Node Network (Future)
+- [ ] Node operator role
+- [ ] Decentralized validation
+- [ ] Network rewards
+
+---
+
+## Resources
+
+- [Architecture Documentation](./ARCHITECTURE.md)
+- [GoodGo Platform Docs](../../docs/)
+- [Pi Network Whitepaper](https://minepi.com/white-paper) (Inspiration)
+- [Wallet Service Integration](../wallet-service-net/)
+
+## License
+
+Proprietary - GoodGo Platform
diff --git a/services/mining-service-net/docs/vi/ARCHITECTURE.md b/services/mining-service-net/docs/vi/ARCHITECTURE.md
new file mode 100644
index 00000000..c42f05e3
--- /dev/null
+++ b/services/mining-service-net/docs/vi/ARCHITECTURE.md
@@ -0,0 +1,467 @@
+# Kiến Trúc Mining Service
+
+## Tổng Quan
+
+Mining Service cung cấp hệ thống đào điểm game hóa lấy cảm hứng từ Pi Network, cho phép người dùng tích lũy Mining Points thông qua hoạt động hàng ngày và xây dựng cộng đồng.
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+graph TD
+ subgraph API["🌐 Tầng API"]
+ Controllers[Controllers]
+ Commands[Commands]
+ Queries[Queries]
+ end
+
+ subgraph Domain["⚙️ Tầng Domain"]
+ Miner[Miner Aggregate]
+ Circle[Circle Aggregate]
+ Referral[Referral Aggregate]
+ end
+
+ subgraph Infra["💾 Tầng Infrastructure"]
+ EF[EF Core]
+ Redis[(Redis Cache)]
+ RabbitMQ[RabbitMQ]
+ end
+
+ subgraph Data["🗄️ Lưu Trữ Dữ Liệu"]
+ DB[(PostgreSQL)]
+ end
+
+ API --> Domain
+ Domain --> Infra
+ EF --> DB
+
+ style API fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
+ style Domain fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+ style Infra fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
+ style Data fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
+```
+
+## Các Mẫu Kiến Trúc
+
+### Domain-Driven Design (DDD)
+
+- **Aggregates**: Miner, Circle, Referral
+- **Entities**: MiningSession, MiningHistory, CircleMember
+- **Value Objects**: MiningRate, MiningPoints, MiningSession
+- **Domain Events**: MiningSessionStarted, PointsMined, CircleCompleted, ReferralActivated
+
+### CQRS Pattern
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ subgraph Write["📝 Commands (Ghi)"]
+ C1[StartMiningCommand]
+ C2[ClaimRewardCommand]
+ C3[CreateCircleCommand]
+ C4[InviteToCircleCommand]
+ C5[ApplyReferralCommand]
+ end
+
+ subgraph Read["📖 Queries (Đọc)"]
+ Q1[GetMinerStatusQuery]
+ Q2[GetMiningHistoryQuery]
+ Q3[GetCircleQuery]
+ Q4[GetReferralsQuery]
+ Q5[GetLeaderboardQuery]
+ end
+
+ style Write fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style Read fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+```
+
+## Domain Model
+
+### Miner Aggregate
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class Miner {
+ +Guid Id
+ +Guid UserId
+ +MinerRole Role
+ +decimal TotalMinedPoints
+ +MiningRate CurrentRate
+ +MiningSession ActiveSession
+ +string ReferralCode
+ +Guid ReferredBy
+ +MinerStatus Status
+ +StartMiningSession()
+ +ClaimMiningReward()
+ +UpgradeRole()
+ +RecalculateMiningRate()
+ }
+
+ class MiningSession {
+ +Guid SessionId
+ +DateTime StartTime
+ +DateTime EndTime
+ +decimal AccumulatedPoints
+ +SessionStatus Status
+ }
+
+ class MiningRate {
+ +decimal BaseRate
+ +decimal CircleBonus
+ +decimal ReferralBonus
+ +decimal RoleBonus
+ +decimal TotalRate
+ }
+
+ class MiningHistory {
+ +Guid Id
+ +decimal PointsEarned
+ +string Source
+ +DateTime EarnedAt
+ }
+
+ Miner "1" --> "0..1" MiningSession : có
+ Miner --> MiningRate : sử dụng
+ Miner "1" --> "*" MiningHistory : theo dõi
+```
+
+### Circle Aggregate
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class Circle {
+ +Guid Id
+ +Guid OwnerId
+ +string Name
+ +List~CircleMember~ Members
+ +decimal TrustScore
+ +decimal BonusMultiplier
+ +CircleStatus Status
+ +AddMember()
+ +RemoveMember()
+ +CalculateTrustScore()
+ +Validate()
+ }
+
+ class CircleMember {
+ +Guid Id
+ +Guid MinerId
+ +DateTime JoinedAt
+ +bool IsActive
+ }
+
+ Circle "1" --> "3..5" CircleMember : chứa
+```
+
+### Referral Aggregate
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+classDiagram
+ class Referral {
+ +Guid Id
+ +Guid ReferrerId
+ +Guid ReferredId
+ +string ReferralCode
+ +decimal BonusRate
+ +bool IsActive
+ +int Level
+ +DateTime CreatedAt
+ +Activate()
+ +Deactivate()
+ +CalculateBonus()
+ }
+```
+
+## Công Thức Tỷ Lệ Đào
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ Base["🎯 Tỷ Lệ Cơ Bản
0.25 MP/giờ"] --> Multiply1((×))
+ Role["👤 Thưởng Vai Trò
+0-50%"] --> Multiply1
+ Multiply1 --> Multiply2((×))
+ Circle["🔵 Thưởng Vòng Tròn
+25%"] --> Multiply2
+ Multiply2 --> Multiply3((×))
+ Referral["👥 Thưởng Giới Thiệu
+25%/người"] --> Multiply3
+ Multiply3 --> Total["✅ Tỷ Lệ Tổng
MP/giờ"]
+
+ style Base fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:2px
+ style Role fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
+ style Circle fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+ style Referral fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style Total fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:3px
+```
+
+**Ví Dụ Tính Toán:**
+| Thành Phần | Giá Trị | Hệ Số |
+|------------|---------|-------|
+| Tỷ Lệ Cơ Bản | 0.25 MP/giờ | - |
+| Vai Trò (Ambassador) | +25% | × 1.25 |
+| Vòng Tròn Hợp Lệ | +25% | × 1.25 |
+| 2 Giới Thiệu | +50% | × 1.50 |
+| **Tổng** | **0.585 MP/giờ** | **14.04 MP/ngày** |
+
+## Database Schema
+
+### ER Diagram
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+erDiagram
+ Miner ||--o{ MiningSession : "có"
+ Miner ||--o{ MiningHistory : "theo dõi"
+ Miner ||--o| Circle : "sở hữu"
+ Circle ||--|{ CircleMember : "chứa"
+ Miner ||--o{ Referral : "giới thiệu"
+ Miner ||--o| Referral : "được giới thiệu bởi"
+
+ Miner {
+ uuid Id PK
+ uuid UserId UK
+ string Role
+ decimal TotalMinedPoints
+ string ReferralCode UK
+ uuid ReferredBy FK
+ string Status
+ datetime CreatedAt
+ }
+
+ MiningSession {
+ uuid Id PK
+ uuid MinerId FK
+ datetime StartTime
+ datetime EndTime
+ decimal AccumulatedPoints
+ string Status
+ }
+
+ MiningHistory {
+ uuid Id PK
+ uuid MinerId FK
+ decimal PointsEarned
+ string Source
+ datetime EarnedAt
+ }
+
+ Circle {
+ uuid Id PK
+ uuid OwnerId FK
+ string Name
+ decimal TrustScore
+ decimal BonusMultiplier
+ string Status
+ }
+
+ CircleMember {
+ uuid Id PK
+ uuid CircleId FK
+ uuid MinerId FK
+ datetime JoinedAt
+ bool IsActive
+ }
+
+ Referral {
+ uuid Id PK
+ uuid ReferrerId FK
+ uuid ReferredId FK
+ string ReferralCode
+ decimal BonusRate
+ bool IsActive
+ int Level
+ }
+```
+
+### Các Index Quan Trọng
+
+| Index | Cột | Mục Đích |
+|-------|-----|----------|
+| `IX_Miners_UserId` | UserId | Tra cứu nhanh theo user |
+| `IX_Miners_ReferralCode` | ReferralCode | Tra cứu mã giới thiệu |
+| `IX_MiningSessions_MinerId_Status` | MinerId, Status | Kiểm tra phiên hoạt động |
+| `IX_Referrals_ReferrerId` | ReferrerId | Danh sách giới thiệu |
+
+## Luồng API
+
+### Bắt Đầu Phiên Đào
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+sequenceDiagram
+ participant Client as 📱 Client
+ participant API as 🌐 Mining API
+ participant Handler as ⚙️ CommandHandler
+ participant Miner as 👤 MinerAggregate
+ participant Calc as 🧮 RateCalculator
+ participant DB as 💾 PostgreSQL
+ participant Cache as ⚡ Redis
+
+ Client->>API: POST /api/v1/mining/start
+ API->>Handler: StartMiningCommand
+ Handler->>Miner: ValidateNoActiveSession()
+ Handler->>Calc: CalculateMiningRate()
+ Calc-->>Handler: MiningRate
+ Handler->>Miner: CreateSession(24 giờ)
+ Miner->>DB: SaveSession()
+ Miner->>Cache: CacheSessionInfo(ttl: 24h)
+ API-->>Client: 200 OK { session_id, rate, end_time }
+```
+
+### Nhận Thưởng Đào
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+sequenceDiagram
+ participant Client as 📱 Client
+ participant API as 🌐 Mining API
+ participant Handler as ⚙️ CommandHandler
+ participant Miner as 👤 MinerAggregate
+ participant DB as 💾 PostgreSQL
+ participant MQ as 📨 RabbitMQ
+
+ Client->>API: POST /api/v1/mining/claim
+ API->>Handler: ClaimRewardCommand
+ Handler->>Miner: GetActiveSession()
+
+ alt Phiên Đã Hoàn Thành
+ Miner->>Miner: CalculateEarnedPoints()
+ Miner->>Miner: AddToTotalPoints()
+ Miner->>DB: SaveMiningHistory()
+ Miner->>MQ: Publish PointsMinedEvent
+ API-->>Client: 200 OK { earned_points, total_points }
+ else Phiên Chưa Sẵn Sàng
+ API-->>Client: 400 Bad Request
+ end
+```
+
+## Giao Tiếp Liên Dịch Vụ
+
+### Phụ Thuộc Dịch Vụ
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+graph TD
+ subgraph External["🔐 Xác Thực"]
+ IAM[IAM Service]
+ end
+
+ subgraph Core["⛏️ Mining Service"]
+ Mining[Mining Service]
+ end
+
+ subgraph Integration["🔗 Tích Hợp"]
+ Social[Social Service]
+ Wallet[Wallet Service]
+ end
+
+ IAM -->|Xác thực JWT| Mining
+ Social <-->|Dữ liệu bạn bè| Mining
+ Mining -->|Chuyển đổi điểm| Wallet
+
+ style External fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
+ style Core fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:3px
+ style Integration fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
+```
+
+### Integration Events (RabbitMQ)
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart LR
+ subgraph Publishers["📤 Nhà Xuất Bản"]
+ IAM[IAM Service]
+ Social[Social Service]
+ Mining1[Mining Service]
+ end
+
+ subgraph Events["📨 Sự Kiện"]
+ E1[UserRegisteredEvent]
+ E2[FriendAddedEvent]
+ E3[PointsMinedEvent]
+ end
+
+ subgraph Consumers["📥 Người Tiêu Thụ"]
+ Mining2[Mining Service]
+ Wallet[Wallet Service]
+ end
+
+ IAM --> E1 --> Mining2
+ Social --> E2 --> Mining2
+ Mining1 --> E3 --> Wallet
+
+ style Events fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+```
+
+## Triển Khai
+
+### Docker Compose
+
+```yaml
+mining-service:
+ build:
+ context: ../..
+ dockerfile: services/mining-service-net/Dockerfile
+ environment:
+ - DATABASE_URL=${MINING_DATABASE_URL}
+ - REDIS_URL=${REDIS_URL}
+ - RABBITMQ_URL=${RABBITMQ_URL}
+ - JWT_AUTHORITY=${IAM_SERVICE_URL}
+ labels:
+ - traefik.http.routers.mining.rule=PathPrefix(`/api/v1/mining`)
+```
+
+### Health Checks
+
+| Endpoint | Kiểm Tra |
+|----------|----------|
+| `/health/live` | ✅ Service đang chạy |
+| `/health/ready` | ✅ DB + Redis đã kết nối |
+| `/health` | ✅ Trạng thái đầy đủ |
+
+## Bảo Mật
+
+### Giới Hạn Tốc Độ
+- 1 phiên đào mỗi 24 giờ
+- 10 lời mời vòng tròn mỗi ngày
+
+### Chống Gian Lận
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+flowchart TD
+ Request([🚀 Yêu Cầu]) --> Device{🔍 Kiểm Tra Thiết Bị}
+ Device -->|Thiết Bị Mới| Flag[⚠️ Đánh Dấu Xem Xét]
+ Device -->|Đã Biết| IP{🌐 Kiểm Tra IP}
+ IP -->|Đáng Ngờ| Block[❌ Chặn]
+ IP -->|Bình Thường| KYC{🔐 Đã KYC?}
+ KYC -->|Chưa| Limited[⚠️ Tính Năng Hạn Chế]
+ KYC -->|Rồi| Full[✅ Truy Cập Đầy Đủ]
+
+ style Request fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
+ style Block fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
+ style Full fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
+ style Device fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style IP fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+ style KYC fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
+```
+
+## Chiến Lược Cache (Redis)
+
+| Mẫu Key | TTL | Mục Đích |
+|---------|-----|----------|
+| `session:{minerId}` | 24h | Cache phiên hoạt động |
+| `rate:{minerId}` | 1h | Cache tỷ lệ đào |
+| `leaderboard:daily` | 5m | Cache bảng xếp hạng |
+
+## Giám Sát
+
+### Metrics
+- Số phiên đào bắt đầu/ngày
+- Tổng điểm đã đào
+- Số thợ đào hoạt động
+- Tỷ lệ chuyển đổi giới thiệu
+
+### Logging
+- Serilog structured logging
+- Correlation IDs để tracing
+- Tích hợp Prometheus + Grafana
diff --git a/services/mining-service-net/docs/vi/README.md b/services/mining-service-net/docs/vi/README.md
new file mode 100644
index 00000000..bc584429
--- /dev/null
+++ b/services/mining-service-net/docs/vi/README.md
@@ -0,0 +1,447 @@
+# Mining Service .NET
+
+> **EN**: Mining Point management service with Pi Network-inspired mechanism for GoodGo Platform.
+> **VI**: Dịch vụ quản lý Mining Point với cơ chế lấy cảm hứng từ Pi Network cho GoodGo Platform.
+
+## Tổng Quan
+
+Mining Service cung cấp **hệ thống đào điểm thưởng game hóa** lấy cảm hứng từ Pi Network, cho phép người dùng tích lũy Mining Points (MP) thông qua hoạt động hàng ngày, giới thiệu bạn bè và xây dựng cộng đồng.
+
+### Tính Năng Chính
+
+| Tính Năng | Mô Tả |
+|-----------|-------|
+| **Đào Hàng Ngày** | Cơ chế tap-to-mine - người dùng kích hoạt phiên đào hàng ngày |
+| **Tỷ Lệ Đào** | Tỷ lệ cơ bản tăng thông qua giới thiệu và xây dựng vòng tròn |
+| **Vòng Tròn An Toàn** | Nhóm tin cậy giúp tăng tỷ lệ đào và bảo mật mạng |
+| **Hệ Thống Giới Thiệu** | Thưởng giới thiệu đa cấp cho sự phát triển mạng lưới |
+| **Vai Trò Người Dùng** | Các cấp Pioneer, Contributor, Ambassador, Node Operator |
+| **Chuyển Đổi Điểm** | Chuyển đổi Mining Points thành điểm thưởng nền tảng |
+
+---
+
+## Thiết Kế Kiến Trúc
+
+### Kiến Trúc Hệ Thống
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ API Gateway (Traefik) │
+└────────────────────────────────────┬────────────────────────────────┘
+ │
+┌────────────────────────────────────▼────────────────────────────────┐
+│ Mining Service .NET │
+├─────────────────────────────────────────────────────────────────────┤
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Mining │ │ Circle │ │ Referral │ │ Rate │ │
+│ │ Session │ │ Manager │ │ Tracker │ │ Calculator │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────────────────┤
+│ Domain Layer │
+│ ┌─────────────────────┐ ┌─────────────────────┐ │
+│ │ MinerAggregate │ │ CircleAggregate │ │
+│ │ - MiningSession │ │ - CircleMember │ │
+│ │ - MiningHistory │ │ - TrustLevel │ │
+│ │ - MiningRate │ │ - CircleBonus │ │
+│ └─────────────────────┘ └─────────────────────┘ │
+├─────────────────────────────────────────────────────────────────────┤
+│ Infrastructure Layer │
+│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
+│ │ PostgreSQL │ │ Redis │ │ RabbitMQ │ │
+│ │ (EF Core) │ │ (Cache) │ │ (Events) │ │
+│ └──────────────┘ └──────────────┘ └──────────────┘ │
+└─────────────────────────────────────────────────────────────────────┘
+ │
+ ┌────────────────────────┼────────────────────────┐
+ ▼ ▼ ▼
+┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ IAM Service │ │ Wallet Service │ │ Social Service │
+│ (Auth/Users) │ │ (Chuyển Điểm) │ │ (Bạn Bè) │
+└───────────────┘ └─────────────────┘ └─────────────────┘
+```
+
+### Cấu Trúc Clean Architecture
+
+```
+mining-service-net/
+├── src/
+│ ├── MiningService.API/ # Tầng API
+│ │ ├── Controllers/
+│ │ │ ├── MiningController.cs # APIs phiên đào
+│ │ │ ├── CirclesController.cs # APIs vòng tròn an toàn
+│ │ │ └── ReferralsController.cs # APIs giới thiệu
+│ │ └── Application/
+│ │ ├── Commands/ # Thao tác ghi
+│ │ └── Queries/ # Thao tác đọc
+│ │
+│ ├── MiningService.Domain/ # Tầng Domain
+│ │ ├── AggregatesModel/
+│ │ │ ├── MinerAggregate/ # Hồ sơ đào của người dùng
+│ │ │ ├── CircleAggregate/ # Vòng tròn an toàn
+│ │ │ └── ReferralAggregate/ # Theo dõi giới thiệu
+│ │ ├── Events/ # Domain events
+│ │ ├── Exceptions/ # Domain exceptions
+│ │ └── Services/ # Domain services
+│ │
+│ └── MiningService.Infrastructure/ # Tầng Infrastructure
+│ ├── EntityConfigurations/ # EF Core mappings
+│ ├── Repositories/ # Truy cập dữ liệu
+│ └── MiningServiceContext.cs # DbContext
+│
+├── tests/
+│ ├── MiningService.UnitTests/
+│ └── MiningService.FunctionalTests/
+│
+├── docs/
+│ ├── en/
+│ └── vi/
+│
+└── Dockerfile
+```
+
+---
+
+## Domain Model
+
+### Core Aggregates
+
+#### 1. Miner Aggregate (Hồ Sơ Đào Của Người Dùng)
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ Miner (Entity) │
+├────────────────────────────────────────────────────────────────────┤
+│ Thuộc Tính: │
+│ - Id: Guid │
+│ - UserId: Guid (từ IAM Service) │
+│ - Role: MinerRole (Pioneer/Contributor/Ambassador/NodeOperator) │
+│ - TotalMinedPoints: decimal │
+│ - CurrentMiningRate: MiningRate (Value Object) │
+│ - CurrentSession: MiningSession? │
+│ - SecurityCircle: Circle? │
+│ - ReferralCode: string │
+│ - ReferredBy: Guid? │
+│ - Status: MinerStatus (Active/Suspended/Banned) │
+│ - CreatedAt: DateTime │
+├────────────────────────────────────────────────────────────────────┤
+│ Hành Vi: │
+│ - StartMiningSession() → MiningSession │
+│ - ClaimMiningReward() → MiningPoints │
+│ - UpgradeRole(role) → void │
+│ - JoinCircle(circle) → void │
+│ - RecalculateMiningRate() → MiningRate │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+#### 2. Circle Aggregate (Vòng Tròn An Toàn)
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ Circle (Entity) │
+├────────────────────────────────────────────────────────────────────┤
+│ Thuộc Tính: │
+│ - Id: Guid │
+│ - OwnerId: Guid (Miner tạo vòng tròn) │
+│ - Members: List (tối đa 5) │
+│ - Name: string │
+│ - TrustScore: decimal (0-100) │
+│ - BonusMultiplier: decimal │
+│ - Status: CircleStatus (Active/Incomplete/Disbanded) │
+│ - CreatedAt: DateTime │
+├────────────────────────────────────────────────────────────────────┤
+│ Hành Vi: │
+│ - AddMember(miner) → void │
+│ - RemoveMember(minerId) → void │
+│ - CalculateTrustScore() → decimal │
+│ - CalculateBonusMultiplier() → decimal │
+│ - Validate() → bool (tối thiểu 3 thành viên) │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+#### 3. Referral Aggregate (Giới Thiệu)
+
+```
+┌────────────────────────────────────────────────────────────────────┐
+│ Referral (Entity) │
+├────────────────────────────────────────────────────────────────────┤
+│ Thuộc Tính: │
+│ - Id: Guid │
+│ - ReferrerId: Guid (người mời) │
+│ - ReferredId: Guid (người được mời) │
+│ - ReferralCode: string │
+│ - BonusRate: decimal │
+│ - IsActive: bool │
+│ - Level: int (1 = trực tiếp, 2 = gián tiếp) │
+│ - CreatedAt: DateTime │
+├────────────────────────────────────────────────────────────────────┤
+│ Hành Vi: │
+│ - Activate() → void │
+│ - Deactivate() → void │
+│ - CalculateBonus(baseRate) → decimal │
+└────────────────────────────────────────────────────────────────────┘
+```
+
+### Value Objects
+
+```csharp
+/// Tính toán tỷ lệ đào
+public record MiningRate(
+ decimal BaseRate, // Mặc định: 0.25 MP/giờ
+ decimal CircleBonus, // +0.25x cho vòng tròn hợp lệ
+ decimal ReferralBonus, // +25% mỗi giới thiệu hoạt động
+ decimal RoleBonus, // Dựa trên cấp vai trò
+ decimal TotalRate // Tỷ lệ tổng hợp
+);
+
+/// Theo dõi phiên đào
+public record MiningSession(
+ Guid SessionId,
+ DateTime StartTime,
+ DateTime EndTime, // StartTime + 24 giờ
+ decimal AccumulatedPoints,
+ MiningSessionStatus Status // Active/Completed/Expired
+);
+
+/// Mining Points
+public record MiningPoints(
+ decimal Amount,
+ DateTime EarnedAt,
+ string Source // Mining/Referral/CircleBonus/RoleBonus
+);
+```
+
+---
+
+## Cơ Chế Đào
+
+### Công Thức Tỷ Lệ Đào
+
+```
+Tỷ Lệ Đào Tổng = Tỷ Lệ Cơ Bản × (1 + Hệ Số Vai Trò) × (1 + Thưởng Vòng Tròn) × (1 + Thưởng Giới Thiệu)
+
+Trong đó:
+- Tỷ Lệ Cơ Bản: 0.25 MP/giờ (có thể cấu hình)
+- Hệ Số Vai Trò: Pioneer=0%, Contributor=10%, Ambassador=25%, Node=50%
+- Thưởng Vòng Tròn: 25% nếu vòng tròn hợp lệ (3-5 thành viên tin cậy)
+- Thưởng Giới Thiệu: 25% mỗi giới thiệu trực tiếp hoạt động (giới hạn 100%)
+```
+
+### Luồng Phiên Đào
+
+```mermaid
+%%{init: {'theme':'dark'}}%%
+sequenceDiagram
+ participant U as 📱 Người Dùng
+ participant A as 🌐 Mining API
+ participant M as ⚙️ MinerAggregate
+ participant R as 🧮 RateCalculator
+ participant DB as 💾 PostgreSQL
+ participant C as ⚡ Redis Cache
+
+ U->>A: POST /api/v1/mining/start
+ A->>M: StartMiningSession()
+ M->>R: CalculateMiningRate()
+ R-->>M: MiningRate
+ M->>M: CreateSession(24 giờ)
+ M->>DB: SaveSession()
+ M->>C: CacheSessionInfo()
+ A-->>U: 200 OK { session_id, rate, end_time }
+
+ Note over U: 24 giờ sau...
+
+ U->>A: POST /api/v1/mining/claim
+ A->>M: ClaimMiningReward()
+ M->>M: CalculateEarnedPoints()
+ M->>M: AddToTotalPoints()
+ M->>DB: SaveMiningHistory()
+ A-->>U: 200 OK { earned_points, total_points }
+```
+
+### Vai Trò & Quyền Lợi Người Dùng
+
+| Vai Trò | Yêu Cầu | Thưởng Đào | Quyền Lợi |
+|---------|---------|------------|-----------|
+| **Pioneer** | Đăng ký | 0% | Đào cơ bản |
+| **Contributor** | Vòng tròn an toàn hợp lệ (3+ thành viên) | +10% | Thưởng vòng tròn hoạt động |
+| **Ambassador** | 5+ giới thiệu hoạt động | +25% | Tăng giới hạn thưởng giới thiệu |
+| **Node Operator** | Chạy phần mềm node (tương lai) | +50% | Thưởng mạng |
+
+---
+
+## API Endpoints
+
+### APIs Đào
+
+| Phương Thức | Endpoint | Mô Tả |
+|-------------|----------|-------|
+| `GET` | `/api/v1/mining/me` | Lấy trạng thái đào hiện tại |
+| `POST` | `/api/v1/mining/start` | Bắt đầu phiên đào 24 giờ |
+| `POST` | `/api/v1/mining/claim` | Nhận thưởng đào |
+| `GET` | `/api/v1/mining/history` | Lấy lịch sử đào |
+| `GET` | `/api/v1/mining/rate` | Lấy chi tiết tỷ lệ đào |
+| `GET` | `/api/v1/mining/leaderboard` | Lấy bảng xếp hạng thợ đào hàng đầu |
+
+### APIs Vòng Tròn An Toàn
+
+| Phương Thức | Endpoint | Mô Tả |
+|-------------|----------|-------|
+| `GET` | `/api/v1/circles/me` | Lấy vòng tròn an toàn của tôi |
+| `POST` | `/api/v1/circles` | Tạo vòng tròn an toàn |
+| `POST` | `/api/v1/circles/invite` | Mời thành viên vào vòng tròn |
+| `POST` | `/api/v1/circles/accept/{inviteId}` | Chấp nhận lời mời vòng tròn |
+| `DELETE` | `/api/v1/circles/members/{memberId}` | Xóa thành viên vòng tròn |
+| `GET` | `/api/v1/circles/trust-score` | Lấy điểm tin cậy vòng tròn |
+
+### APIs Giới Thiệu
+
+| Phương Thức | Endpoint | Mô Tả |
+|-------------|----------|-------|
+| `GET` | `/api/v1/referrals/code` | Lấy mã giới thiệu của tôi |
+| `GET` | `/api/v1/referrals` | Danh sách giới thiệu của tôi |
+| `GET` | `/api/v1/referrals/stats` | Lấy thống kê giới thiệu |
+| `POST` | `/api/v1/referrals/apply` | Áp dụng mã giới thiệu (khi đăng ký) |
+
+### APIs Quản Trị
+
+| Phương Thức | Endpoint | Mô Tả |
+|-------------|----------|-------|
+| `GET` | `/api/v1/admin/miners` | Danh sách tất cả thợ đào (phân trang) |
+| `PUT` | `/api/v1/admin/miners/{id}/suspend` | Tạm ngừng thợ đào |
+| `PUT` | `/api/v1/admin/miners/{id}/ban` | Cấm thợ đào |
+| `PUT` | `/api/v1/admin/config/mining-rate` | Cập nhật tỷ lệ đào cơ bản |
+
+---
+
+## Điểm Tích Hợp
+
+### Phụ Thuộc Dịch Vụ
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ Mining Service │
+├─────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌──────────────┐ Xác Thực ┌──────────────────────┐ │
+│ │ │◄────────────────────────│ IAM Service │ │
+│ │ │ │ - Xác thực user │ │
+│ │ │ │ - JWT tokens │ │
+│ │ │ └──────────────────────┘ │
+│ │ │ │
+│ │ Mining │ Chuyển Đổi Điểm ┌──────────────────────┐ │
+│ │ Service │────────────────────────►│ Wallet Service │ │
+│ │ │ │ - Chuyển MP sang LP │ │
+│ │ │ │ - Giao dịch │ │
+│ │ │ └──────────────────────┘ │
+│ │ │ │
+│ │ │ Đồ Thị Xã Hội ┌──────────────────────┐ │
+│ │ │◄───────────────────────►│ Social Service │ │
+│ │ │ │ - Danh sách bạn bè │ │
+│ │ │ │ - Xác thực tin cậy │ │
+│ └──────────────┘ └──────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────┘
+```
+
+### Integration Events (RabbitMQ)
+
+| Event | Publisher | Consumer(s) | Mô Tả |
+|-------|-----------|-------------|-------|
+| `MinerCreatedEvent` | Mining Service | IAM Service | Hồ sơ thợ đào mới được tạo |
+| `MiningSessionStartedEvent` | Mining Service | - | Phiên đào đã bắt đầu |
+| `PointsMinedEvent` | Mining Service | Wallet Service | Điểm đã nhận, đồng bộ với ví |
+| `CircleCompletedEvent` | Mining Service | - | Vòng tròn an toàn trở nên hợp lệ |
+| `ReferralActivatedEvent` | Mining Service | - | Giới thiệu trở nên hoạt động |
+| `UserRegisteredEvent` | IAM Service | Mining Service | Tạo hồ sơ thợ đào |
+| `FriendAddedEvent` | Social Service | Mining Service | Cập nhật gợi ý vòng tròn |
+
+---
+
+## Tech Stack
+
+| Thành Phần | Công Nghệ |
+|------------|-----------|
+| Framework | .NET 10 |
+| Database | PostgreSQL (EF Core) |
+| Caching | Redis (StackExchange.Redis) |
+| Message Queue | RabbitMQ (MassTransit) |
+| CQRS | MediatR |
+| Validation | FluentValidation |
+| API Docs | Swagger/OpenAPI |
+| Logging | Serilog |
+| Observability | Prometheus + Grafana |
+
+---
+
+## Cấu Hình
+
+### Biến Môi Trường
+
+| Biến | Mô Tả | Bắt Buộc | Mặc Định |
+|------|-------|----------|----------|
+| `DATABASE_URL` | Kết nối PostgreSQL | Có | - |
+| `REDIS_URL` | Kết nối Redis | Có | - |
+| `RABBITMQ_URL` | Kết nối RabbitMQ | Có | - |
+| `JWT_AUTHORITY` | URL JWT issuer | Có | - |
+| `MINING_BASE_RATE` | Tỷ lệ đào cơ bản (MP/giờ) | Không | 0.25 |
+| `MINING_SESSION_HOURS` | Thời gian phiên | Không | 24 |
+| `CIRCLE_MIN_MEMBERS` | Số thành viên tối thiểu vòng tròn | Không | 3 |
+| `CIRCLE_MAX_MEMBERS` | Số thành viên tối đa vòng tròn | Không | 5 |
+| `REFERRAL_BONUS_PERCENT` | Thưởng mỗi giới thiệu | Không | 25 |
+| `REFERRAL_BONUS_CAP` | Giới hạn thưởng giới thiệu | Không | 100 |
+
+---
+
+## Xem Xét Bảo Mật
+
+### Biện Pháp Chống Gian Lận
+
+1. **Giới Hạn Tốc Độ** - Tối đa 1 phiên đào mỗi 24 giờ
+2. **Vân Tay Thiết Bị** - Theo dõi thay đổi thiết bị, đánh dấu hoạt động đáng ngờ
+3. **Giám Sát IP** - Phát hiện nhiều tài khoản từ cùng IP
+4. **Xác Thực Vòng Tròn** - Thành viên phải là người dùng thật, hoạt động
+5. **Xác Minh Giới Thiệu** - Giới thiệu phải vượt qua KYC để kích hoạt thưởng
+6. **Điểm Hoạt Động** - Phạt các mẫu không hoạt động hoặc giống bot
+
+### Bảo Vệ Dữ Liệu
+
+- Lịch sử đào được mã hóa khi lưu trữ
+- Dữ liệu cá nhân tuân thủ GDPR
+- Nhật ký kiểm toán cho tất cả hành động quản trị
+
+---
+
+## Roadmap
+
+### Giai Đoạn 1: Core Mining (MVP)
+- [x] Tạo hồ sơ thợ đào
+- [x] Phiên đào tap-to-mine hàng ngày
+- [x] Tính toán tỷ lệ đào cơ bản
+- [x] Lịch sử đào
+
+### Giai Đoạn 2: Tính Năng Xã Hội
+- [ ] Vòng tròn an toàn
+- [ ] Hệ thống giới thiệu
+- [ ] Tính toán thưởng vòng tròn
+
+### Giai Đoạn 3: Tính Năng Nâng Cao
+- [ ] Tiến trình vai trò người dùng
+- [ ] Chuyển đổi điểm sang ví
+- [ ] Bảng xếp hạng
+
+### Giai Đoạn 4: Mạng Node (Tương Lai)
+- [ ] Vai trò node operator
+- [ ] Xác thực phi tập trung
+- [ ] Thưởng mạng
+
+---
+
+## Tài Nguyên
+
+- [Tài Liệu Kiến Trúc](./ARCHITECTURE.md)
+- [Tài Liệu GoodGo Platform](../../docs/)
+- [Pi Network Whitepaper](https://minepi.com/white-paper) (Nguồn Cảm Hứng)
+- [Tích Hợp Wallet Service](../wallet-service-net/)
+
+## Giấy Phép
+
+Proprietary - GoodGo Platform