---
# System prepended metadata

title: System Architecture

---

# System Architecture
```mermaid
graph TB
    subgraph Client_Layer["👥 Client Layer"]
        MobileApp[📱 Mobile App<br/>React Native/Flutter]
        WebApp[🌐 Web App<br/>React/Next.js]
        AdminUI[⚙️ Admin Portal<br/>React Admin]
    end
    
    subgraph API_Layer["🚪 API Layer"]
        Gateway[API Gateway/BFF<br/>Kong/NGINX<br/>Authentication, Rate Limit<br/>Request Routing]
    end
    
    subgraph Service_Layer["⚡ Service Layer - Microservices"]
        direction TB
        
        AuthSvc[🔐 Auth Service<br/>Registration, Login,<br/>JWT Management]
        KYCSvc[🪪 KYC Service<br/>eKYC Orchestration,<br/>Identity Verification]
        AccountSvc[💼 Account Service<br/>Account Management,<br/>Balance Query]
        LedgerSvc[📒 Ledger Service ⭐<br/>CORE<br/>Double-Entry Accounting,<br/>Immutable Ledger]
        PaymentSvc[💰 Payment Service<br/>Deposit/Withdrawal,<br/>PSP Orchestration]
        AccrualSvc[📊 Accrual Engine<br/>Daily Interest Calculation,<br/>EOD Job]
        PortfolioSvc[📈 Portfolio Service<br/>Investment Management,<br/>Asset Allocation]
        NotifSvc[📨 Notification Service<br/>Push/Email/SMS]
        InsightSvc[💡 Insight Service<br/>Financial Health,<br/>Recommendations]
    end
    
    subgraph Data_Layer["💾 Data Layer - Database per Service"]
        AuthDB[(🗄️ Auth DB<br/>PostgreSQL<br/>users,<br/>identities,<br/>sessions)]
        KYCDB[(🗄️ KYC DB<br/>PostgreSQL<br/>kyc_profiles,<br/>documents)]
        AccountDB[(🗄️ Account DB<br/>PostgreSQL<br/>accounts,<br/>balances,<br/>partners)]
        LedgerDB[(🗄️ Ledger DB ⭐<br/>PostgreSQL<br/>CRITICAL<br/>ledger_entries,<br/>transactions)]
        PaymentDB[(🗄️ Payment DB<br/>PostgreSQL<br/>deposits,<br/>withdrawals,<br/>bank_accounts)]
        PortfolioDB[(🗄️ Portfolio DB<br/>PostgreSQL<br/>portfolios,<br/>orders,<br/>holdings)]
        NotificationDB[(🗄️ Notification DB<br/>PostgreSQL<br/>templates,<br/>logs)]
        Redis[(⚡ Redis Cache<br/>Session Cache<br/>Balance Cache<br/>OTP Cache)]
    end
    
    subgraph Message_Layer["📮 Message Infrastructure - Event-Driven Communication"]
        MessageBroker[🔔 Message Broker<br/>RabbitMQ / Kafka<br/>Event Bus<br/>Pub/Sub Pattern]
    end
    
    subgraph External_Layer["🌐 External Systems"]
        PSP[💳 VNPT ePay]
        eKYC[🪪 VNPT eKYC]
        Broker[📈 AAS Broker]
        SMS[📨 SMS Gateway]
    end
    
    %% Client to Gateway
    MobileApp -->|HTTPS/JSON| Gateway
    WebApp -->|HTTPS/JSON| Gateway
    AdminUI -->|HTTPS/JSON| Gateway
    
    %% Gateway to Services
    Gateway -->|Auth| AuthSvc
    Gateway -->|KYC| KYCSvc
    Gateway -->|Account| AccountSvc
    Gateway -->|Payment| PaymentSvc
    Gateway -->|Portfolio| PortfolioSvc
    Gateway -->|Insight| InsightSvc
    
    %% Service to Service (Critical Dependencies)
    PaymentSvc -.->|Post Transaction| LedgerSvc
    AccrualSvc -.->|Post Interest| LedgerSvc
    AccountSvc -.->|Read Balance| LedgerSvc
    PortfolioSvc -.->|Post Transfer| LedgerSvc
    InsightSvc -.->|Read Entries| LedgerSvc
    
    %% Service to Service (Synchronous - Direct API calls)
    PaymentSvc -->|Validate Balance| AccountSvc
    KYCSvc -->|Update Status| AuthSvc
    
    %% Service to Message Broker (Asynchronous - Event-Driven)
    PaymentSvc -.->|Publish Events| MessageBroker
    KYCSvc -.->|Publish Events| MessageBroker
    PortfolioSvc -.->|Publish Events| MessageBroker
    AccrualSvc -.->|Publish Events| MessageBroker
    
    %% Message Broker to Services (Subscribers)
    MessageBroker -.->|Subscribe Events| NotifSvc
    MessageBroker -.->|Subscribe Events| InsightSvc
    MessageBroker -.->|Subscribe Events| AccountSvc
    
    %% Service to Database
    AuthSvc --> AuthDB
    KYCSvc --> KYCDB
    AccountSvc --> AccountDB
    LedgerSvc --> LedgerDB
    PaymentSvc --> PaymentDB
    PortfolioSvc --> PortfolioDB
    NotifSvc --> NotificationDB
    AccrualSvc -.->|Read/Write| AccountDB
    InsightSvc -.->|Read-only| LedgerDB
    
    %% Service to Cache
    AuthSvc -.->|Session & OTP| Redis
    AccountSvc -.->|Balance Cache| Redis
    PaymentSvc -.->|Idempotency Check| Redis
    
    %% Service to External
    PaymentSvc -->|Init Payment| PSP
    PSP -.->|Webhook Result| PaymentSvc
    KYCSvc -->|Init eKYC| eKYC
    eKYC -.->|Webhook Result| KYCSvc
    PortfolioSvc -->|Place Order| Broker
    NotifSvc -->|Send OTP| SMS
    
    %% Styling
    style LedgerSvc fill:#ff6b6b,stroke:#c92a2a,stroke-width:4px,color:#fff
    style LedgerDB fill:#ff6b6b,stroke:#c92a2a,stroke-width:4px,color:#fff
    style Gateway fill:#4dabf7,stroke:#1971c2,stroke-width:3px
    style MessageBroker fill:#ffd43b,stroke:#f59f00,stroke-width:3px
    style Redis fill:#ffd43b,stroke:#f59f00,stroke-width:2px
    style AuthDB fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style KYCDB fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style AccountDB fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style PaymentDB fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style PortfolioDB fill:#fce4ec,stroke:#c2185b,stroke-width:2px
    style NotificationDB fill:#e0f2f1,stroke:#00897b,stroke-width:2px
```

#### **WealthHup Platform Components**

**Client Layer**

Đây là tầng mà người dùng tương tác trực tiếp:

- **Mobile App**: Ứng dụng di động (iOS/Android) cho khách hàng
- **Web App**: Giao diện web cho khách hàng
- **Admin Portal**: Công cụ quản trị cho admin/ops

Tất cả đều giao tiếp với backend qua **HTTPS/JSON REST API**.

#### **API Layer**

**API Gateway/BFF (Backend For Frontend)** là điểm vào duy nhất:

1. **Authentication**: Verify JWT token
2. **Rate Limiting**: Giới hạn request (ví dụ: 100 req/minute)
3. **Request Routing**: Chuyển request đến service phù hợp
4. **Response Aggregation**: Gộp response từ nhiều service
5. **Logging & Monitoring**: Ghi log mọi request


#### **Service Layer Microservices)**

Mỗi service có **một trách nhiệm cụ thể** (Single Responsibility Principle):

##### 1. **Auth Service (Xác thực & Phân quyền)**

**Mục đích**: Quản lý người dùng và session

**Chức năng**:
- Đăng ký tài khoản (register)
- Đăng nhập (login)
- Phát hành JWT token
- Refresh token
- Quản lý session

**Database**: `AuthDB` (users, sessions, otp_logs, user_identities, user_audit_logs)


##### 2. **KYC Service (Xác thực danh tính)**

**Mục đích**: Orchestrate quy trình eKYC

**Chức năng**:
- Tạo session eKYC với VNPT
- Nhận webhook từ eKYC provider
- Kiểm tra kết quả C06
- Cập nhật trạng thái KYC của user

**Database**: `KYCDB` (kyc_profiles, kyc_documents, kyc_audit_logs)

##### 3. **Account Service (Quản lý tài khoản)**

**Mục đích**: Quản lý tài khoản logic của khách hàng

**Chức năng**:
- Tạo/Đóng tài khoản
- Truy vấn số dư (derived từ Ledger)
- Lock/Unlock balance
- Quản lý multi-partner accounts

**Database**: `AccountDB` (customers, accounts, account_balances, partners, accrual_logs)

**Quan trọng**: 
- **KHÔNG được update balance trực tiếp**
- Balance = derived từ Ledger Service
- `account_balances` là materialized view (cache)

##### 4. **🌟 Ledger Service (CORE)**

**Mục đích**: Single Source of Truth cho mọi giao dịch tài chính

**Chức năng**:
- Post ledger transaction (double-entry)
- Reverse transaction (khi cần)
- Query ledger entries
- Calculate balance từ entries

**Database**: `LedgerDB` (ledger_accounts, ledger_transactions, ledger_entries, ledger_account_snapshots, ledger_revesal_log)

**Nguyên tắc thiết kế**:
- **Immutable**: Ledger entries KHÔNG BAO GIỜ được update/delete
- **Double-entry**: Luôn cân bằng (Σ DR = Σ CR)
- **Append-only**: Chỉ INSERT, không UPDATE
- **No dependencies**: Ledger KHÔNG gọi service khác
- **Internal only**: API chỉ dùng nội bộ, không expose ra ngoài

**Luồng dependency**:
```
Ledger Service (CORE)
    ▲
    │ (Chỉ có chiều này, KHÔNG có chiều ngược lại)
    │
    ├── Payment Service (post transaction)
    ├── Accrual Engine (post interest)
    ├── Portfolio Service (post transfer)
    ├── Account Service (read balance)
    └── Insight Service (read entries)
```

##### 5. **Payment Service (Nạp/Rút tiền)**

**Mục đích**: Orchestrate quy trình nạp/rút tiền qua PSP

**Chức năng**:
- Init deposit (khởi tạo nạp tiền)
- Init withdrawal (khởi tạo rút tiền)
- Nhận webhook từ PSP
- Post transaction to Ledger
- Handle idempotency (webhook có thể retry nhiều lần)

**Database**: `PaymentDB` (deposits, withdrawals, bank_accounts, psp_webhooks, withdrawal_fee_config)

##### 6. **Accrual Engine (Tính lãi tự động)**

**Mục đích**: Tính và ghi nhận lãi hàng ngày

**Chức năng**:
- Chạy EOD (End of Day) job
- Tính lãi cho tất cả Flexible Accounts
- Post interest entries to Ledger

**Database**: Sử dụng `AccountDB` để lưu accrual logs

##### 7. **Portfolio Service (Quản lý danh mục đầu tư)**

**Mục đích**: Orchestrate việc đầu tư vào partner products

**Chức năng**:
- Hiển thị danh sách sản phẩm
- Validate risk profile
- Place order to broker
- Track investment performance

**Database**: `PortfolioDB` (portfolios, products, portfolio_hodings, orders, order_executions)


##### 8. **Notification Service (Thông báo)**

**Mục đích**: Gửi thông báo cho người dùng

**Chức năng**:
- Push notification
- Email
- SMS

**Database**: `NotificationDB` (notification_templates, notification_logs, user_notification_preferences)

**Characteristic**
- **Multi-channel**: PUSH, EMAIL, SMS
- **Template-based**: Tái sử dụng templates với variables
- **Retry mechanism**: Tự động retry khi failed (max 3 lần)
- **User preferences**: User có thể tắt/bật từng kênh


##### 9. **Insight Service**

**Mục đích**: Cung cấp insights tài chính cho người dùng

**Chức năng**:
- Financial Health score
- Asset allocation analysis
- Recommendations

#### **Message Infrastructure Layer (Event-Driven Communication)**

**Message Broker** (RabbitMQ / Apache Kafka / AWS SNS+SQS):

**Mục đích**: 
- Decoupling services thông qua asynchronous events
- Pub/Sub pattern cho event-driven architecture
- Reliable message delivery với retry mechanism

**Use Cases trong WealthHup**:

| Event Type | Publisher | Subscribers | Purpose |
|------------|-----------|-------------|---------|
| **DepositSuccessEvent** | Payment Service | Notification Service, Insight Service | Thông báo nạp tiền thành công |
| **WithdrawalInitiatedEvent** | Payment Service | Notification Service | Thông báo rút tiền đang xử lý |
| **KYCApprovedEvent** | KYC Service | Account Service, Notification Service | Tạo account sau KYC |
| **InterestAccruedEvent** | Accrual Engine | Notification Service, Insight Service | Thông báo lãi được cộng |
| **OrderExecutedEvent** | Portfolio Service | Notification Service, Insight Service | Thông báo lệnh đã khớp |
| **BalanceChangedEvent** | Ledger Service | Account Service | Update balance cache |


#### **Data Layer (Tầng dữ liệu)**

**Database per Service Pattern**: Mỗi service có database riêng

**Lợi ích**:
- **Loose coupling**: Service không share database
- **Independent scaling**: Scale database theo nhu cầu
- **Technology flexibility**: Có thể dùng MongoDB cho một service, PostgreSQL cho service khác

**Redis Cache**:
- Session cache (JWT blacklist, OTP)
- Balance cache (giảm load cho Ledger)
- TTL: 5-15 phút

# Database design
```mermaid
erDiagram
    %% ============================================================
    %% AUTH DATABASE (auth_db) - UPDATED WITH IDENTITY ABSTRACTION
    %% ============================================================
    
    USERS {
        uuid id PK
        varchar password_hash "NULLABLE, for password-based login"
        varchar display_name "NULLABLE"
        varchar status "UNVERIFIED|PENDING_KYC|ACTIVE|SUSPENDED|CLOSED"
        timestamp created_at
        timestamp updated_at
        timestamp last_login_at
    }
    
    USER_IDENTITIES {
        uuid id PK
        uuid user_id FK "NOT NULL"
        varchar identity_type "PHONE|EMAIL|GOOGLE|FACEBOOK|APPLE"
        varchar identity_value "NOT NULL"
        boolean is_verified "DEFAULT FALSE"
        boolean is_primary "DEFAULT FALSE"
        jsonb provider_data "For OAuth providers"
        timestamp verified_at
        timestamp created_at
        timestamp updated_at
    }
    
    AUTH_SESSIONS {
        uuid id PK
        uuid user_id FK "NOT NULL"
        text access_token "NOT NULL"
        text refresh_token UK "NOT NULL"
        varchar device_id
        varchar device_type "iOS|Android|Web"
        inet ip_address
        text user_agent
        timestamp created_at
        timestamp expired_at "NOT NULL"
        timestamp last_used_at
        boolean is_active "DEFAULT TRUE"
    }
    
    OTP_LOGS {
        uuid id PK
        uuid user_identity_id FK "NULLABLE"
        varchar identity_type "PHONE|EMAIL"
        varchar identity_value "NOT NULL"
        varchar otp_code "NOT NULL"
        varchar otp_hash "NOT NULL"
        varchar purpose "REGISTER|LOGIN|RESET_PASSWORD|VERIFY_IDENTITY"
        varchar status "SENT|VERIFIED|EXPIRED|FAILED"
        int attempt_count "DEFAULT 0"
        timestamp created_at
        timestamp expired_at "NOT NULL"
        timestamp verified_at
    }
    
    USER_AUDIT_LOGS {
        uuid id PK
        uuid user_id FK "NOT NULL"
        varchar event_type "NOT NULL"
        jsonb event_data
        inet ip_address
        text user_agent
        uuid performed_by
        timestamp created_at
    }
    
    %% ============================================================
    %% LEDGER DATABASE (ledger_db) - CORE
    %% ============================================================
    
    LEDGER_ACCOUNTS {
        uuid id PK
        varchar code UK "NOT NULL"
        varchar name "NOT NULL"
        text description
        varchar account_type "ASSET|LIABILITY|REVENUE|EXPENSE"
        varchar owner_type "CUSTOMER|PARTNER|PLATFORM|SYSTEM"
        uuid owner_id "NULLABLE"
        varchar currency "DEFAULT VND"
        boolean is_active "DEFAULT TRUE"
        timestamp created_at
        varchar created_by "NOT NULL"
    }
    
    LEDGER_TRANSACTIONS {
        uuid id PK
        varchar transaction_code UK "NOT NULL"
        varchar transaction_type "DEPOSIT|WITHDRAWAL|INTEREST|FEE|TRANSFER|ADJUSTMENT"
        varchar status "INIT|VALIDATED|POSTED|REVERSED"
        varchar source_type "NOT NULL"
        uuid source_id "NOT NULL"
        uuid partner_id "NULLABLE"
        bigint total_amount "NOT NULL"
        varchar currency "DEFAULT VND"
        date posting_date "NOT NULL"
        timestamp created_at
        varchar created_by "NOT NULL"
        jsonb metadata
    }
    
    LEDGER_ENTRIES {
        uuid id PK
        uuid transaction_id FK "NOT NULL"
        uuid ledger_account_id FK "NOT NULL"
        bigint debit_amount "DEFAULT 0"
        bigint credit_amount "DEFAULT 0"
        varchar currency "DEFAULT VND"
        varchar reference_id
        text description
        jsonb metadata
        timestamp created_at "NOT NULL, IMMUTABLE"
    }
    
    LEDGER_REVERSAL_LOG {
        uuid id PK
        uuid original_transaction_id "FK, UK, NOT NULL"
        uuid reversal_transaction_id FK "NOT NULL"
        text reason "NOT NULL"
        varchar performed_by "NOT NULL"
        timestamp reversed_at
    }
    
    LEDGER_ACCOUNT_SNAPSHOTS {
        uuid id PK
        uuid ledger_account_id FK "NOT NULL"
        date snapshot_date "NOT NULL"
        bigint opening_balance "NOT NULL"
        bigint closing_balance "NOT NULL"
        bigint total_debit "NOT NULL"
        bigint total_credit "NOT NULL"
        int transaction_count "NOT NULL"
        timestamp created_at
    }
    
    %% ============================================================
    %% PAYMENT DATABASE (payment_db)
    %% ============================================================
    
    DEPOSITS {
        uuid id PK
        varchar deposit_code UK "NOT NULL"
        uuid user_id "NOT NULL"
        uuid account_id "NOT NULL"
        bigint amount "NOT NULL, CHECK > 0"
        varchar currency "DEFAULT VND"
        varchar psp_provider "DEFAULT VNPT_EPAY"
        varchar psp_txn_id UK "NULLABLE"
        jsonb psp_request
        jsonb psp_response
        text psp_redirect_url
        varchar status "INIT|PENDING|SUCCESS|FAILED|EXPIRED"
        varchar idempotency_key UK "NOT NULL"
        timestamp init_at
        timestamp pending_at
        timestamp completed_at
        timestamp expired_at
        timestamp created_at
        jsonb metadata
    }
    
    WITHDRAWALS {
        uuid id PK
        varchar withdrawal_code UK "NOT NULL"
        uuid user_id "NOT NULL"
        uuid account_id "NOT NULL"
        bigint amount "NOT NULL, CHECK > 0"
        bigint fee "DEFAULT 0"
        bigint net_amount "NOT NULL"
        varchar currency "DEFAULT VND"
        varchar speed "IMMEDIATE|DELAYED"
        uuid bank_account_id FK "NOT NULL"
        varchar psp_provider "DEFAULT VNPT_EPAY"
        varchar psp_txn_id UK "NULLABLE"
        jsonb psp_request
        jsonb psp_response
        varchar status "PENDING|PROCESSING|SUCCESS|FAILED|CANCELLED"
        varchar idempotency_key UK "NOT NULL"
        timestamp requested_at
        timestamp processing_at
        timestamp processed_at
        timestamp estimated_arrival
        timestamp created_at
        jsonb metadata
    }
    
    BANK_ACCOUNTS {
        uuid id PK
        uuid user_id "NOT NULL"
        varchar bank_code "NOT NULL"
        varchar bank_name "NOT NULL"
        varchar branch_name
        varchar account_number "NOT NULL, ENCRYPTED"
        varchar account_name "NOT NULL"
        boolean is_verified "DEFAULT FALSE"
        timestamp verified_at
        varchar verified_by
        boolean is_default "DEFAULT FALSE"
        boolean is_active "DEFAULT TRUE"
        timestamp created_at
        timestamp updated_at
    }
    
    PSP_WEBHOOKS {
        uuid id PK
        varchar webhook_id UK "NOT NULL"
        varchar webhook_type "NOT NULL"
        varchar reference_type "NOT NULL"
        uuid reference_id "NOT NULL"
        jsonb payload "NOT NULL"
        varchar signature "NOT NULL"
        varchar status "RECEIVED|PROCESSING|PROCESSED|IGNORED|FAILED"
        int retry_count "DEFAULT 0"
        timestamp last_retry_at
        timestamp received_at
        timestamp processed_at
        text error_message
    }
    
    WITHDRAWAL_FEE_CONFIG {
        uuid id PK
        varchar speed "NOT NULL"
        varchar fee_type "FIXED|PERCENTAGE"
        bigint fee_value "NOT NULL"
        bigint min_fee
        bigint max_fee
        date effective_from "NOT NULL"
        date effective_to
        boolean is_active "DEFAULT TRUE"
        timestamp created_at
        varchar created_by "NOT NULL"
    }
    
    %% ============================================================
    %% KYC DATABASE (kyc_db)
    %% ============================================================
    
    KYC_PROFILES {
        uuid id PK
        uuid user_id UK "NOT NULL"
        varchar full_name "NOT NULL"
        date date_of_birth "NOT NULL"
        varchar id_number UK "NOT NULL"
        varchar id_type "CCCD|PASSPORT"
        date id_issued_date
        varchar id_issued_place
        varchar gender "MALE|FEMALE|OTHER"
        varchar nationality "DEFAULT VN"
        text address
        varchar kyc_provider "VNPT_EKYC|FPT_EKYC"
        varchar kyc_session_id UK "NULLABLE"
        varchar status "PENDING|PROCESSING|APPROVED|REJECTED|EXPIRED"
        text rejection_reason
        timestamp submitted_at
        timestamp approved_at
        timestamp expired_at
        varchar approved_by
        jsonb kyc_data "ENCRYPTED"
        timestamp created_at
        timestamp updated_at
    }
    
    KYC_DOCUMENTS {
        uuid id PK
        uuid kyc_profile_id FK "NOT NULL"
        varchar document_type "ID_FRONT|ID_BACK|SELFIE|FACE_VIDEO"
        text document_url "NOT NULL"
        varchar storage_path "NOT NULL"
        bigint file_size
        varchar mime_type
        varchar verification_status "PENDING|VERIFIED|REJECTED"
        float confidence_score
        jsonb ocr_data
        jsonb verification_result
        timestamp uploaded_at
        timestamp verified_at
    }
    
    KYC_AUDIT_LOGS {
        uuid id PK
        uuid kyc_profile_id FK "NOT NULL"
        varchar event_type "NOT NULL"
        varchar old_status
        varchar new_status
        text notes
        jsonb event_data
        varchar performed_by
        timestamp created_at
    }
    
    %% ============================================================
    %% ACCOUNT DATABASE (account_db)
    %% ============================================================
    
    CUSTOMERS {
        uuid id PK
        uuid user_id UK "NOT NULL"
        varchar customer_code UK "NOT NULL"
        varchar customer_type "INDIVIDUAL|CORPORATE"
        varchar full_name "NOT NULL"
        date date_of_birth
        varchar phone "NOT NULL"
        varchar email
        text address
        varchar risk_profile "CONSERVATIVE|MODERATE|AGGRESSIVE"
        varchar status "ACTIVE|SUSPENDED|CLOSED"
        timestamp created_at
        timestamp updated_at
        varchar created_by
    }
    
    ACCOUNTS {
        uuid id PK
        uuid customer_id FK "NOT NULL"
        varchar account_code UK "NOT NULL"
        varchar account_type "FLEXIBLE|TERM_DEPOSIT|INVESTMENT"
        varchar currency "DEFAULT VND"
        uuid partner_id "NULLABLE"
        varchar status "ACTIVE|LOCKED|CLOSED"
        timestamp opened_at
        timestamp closed_at
        varchar closed_reason
        timestamp created_at
        varchar created_by
    }
    
    ACCOUNT_BALANCES {
        uuid id PK
        uuid account_id UK, FK "NOT NULL"
        bigint ledger_balance "NOT NULL, DEFAULT 0"
        bigint available_balance "NOT NULL, DEFAULT 0"
        bigint locked_balance "NOT NULL, DEFAULT 0"
        bigint accrued_interest "NOT NULL, DEFAULT 0"
        varchar currency "DEFAULT VND"
        timestamp last_updated
        timestamp synced_at
    }
    
    PARTNERS {
        uuid id PK
        varchar partner_code UK "NOT NULL"
        varchar partner_name "NOT NULL"
        varchar partner_type "BROKER|BANK|FINTECH"
        text description
        jsonb contact_info
        jsonb api_config
        boolean is_active "DEFAULT TRUE"
        timestamp created_at
        varchar created_by
    }
    
    ACCRUAL_LOGS {
        uuid id PK
        uuid account_id FK "NOT NULL"
        date accrual_date "NOT NULL"
        bigint principal_amount "NOT NULL"
        float interest_rate "NOT NULL"
        bigint interest_amount "NOT NULL"
        varchar status "CALCULATED|POSTED|REVERSED"
        uuid ledger_transaction_id "NULLABLE"
        timestamp calculated_at
        timestamp posted_at
        varchar posted_by
    }
    
    %% ============================================================
    %% PORTFOLIO DATABASE (portfolio_db)
    %% ============================================================
    
    PORTFOLIOS {
        uuid id PK
        uuid customer_id "NOT NULL"
        uuid account_id FK "NOT NULL"
        varchar portfolio_code UK "NOT NULL"
        varchar portfolio_name "NOT NULL"
        varchar strategy "BALANCED|GROWTH|INCOME|CONSERVATIVE"
        varchar status "ACTIVE|SUSPENDED|CLOSED"
        timestamp created_at
        timestamp updated_at
    }
    
    PRODUCTS {
        uuid id PK
        varchar product_code UK "NOT NULL"
        varchar product_name "NOT NULL"
        varchar product_type "STOCK|BOND|FUND|GOLD|CRYPTO"
        uuid partner_id FK "NOT NULL"
        varchar currency "DEFAULT VND"
        varchar risk_level "LOW|MEDIUM|HIGH"
        bigint min_investment
        text description
        jsonb product_metadata
        boolean is_active "DEFAULT TRUE"
        timestamp created_at
    }
    
    PORTFOLIO_HOLDINGS {
        uuid id PK
        uuid portfolio_id FK "NOT NULL"
        uuid product_id FK "NOT NULL"
        float quantity "NOT NULL, DEFAULT 0"
        bigint avg_cost_price "NOT NULL"
        bigint market_value "NOT NULL"
        bigint unrealized_pnl "NOT NULL"
        timestamp last_updated
    }
    
    ORDERS {
        uuid id PK
        uuid portfolio_id FK "NOT NULL"
        uuid product_id FK "NOT NULL"
        varchar order_code UK "NOT NULL"
        varchar order_type "BUY|SELL"
        float quantity "NOT NULL"
        bigint price "NOT NULL"
        bigint total_amount "NOT NULL"
        bigint fee "DEFAULT 0"
        varchar status "PENDING|SUBMITTED|PARTIAL_FILLED|FILLED|CANCELLED|REJECTED"
        uuid partner_id FK "NOT NULL"
        varchar partner_order_id UK "NULLABLE"
        jsonb partner_request
        jsonb partner_response
        timestamp submitted_at
        timestamp filled_at
        timestamp created_at
        varchar created_by
    }
    
    ORDER_EXECUTIONS {
        uuid id PK
        uuid order_id FK "NOT NULL"
        float executed_quantity "NOT NULL"
        bigint executed_price "NOT NULL"
        bigint executed_amount "NOT NULL"
        varchar execution_id UK "NOT NULL"
        timestamp executed_at
    }
    
    %% ============================================================
    %% NOTIFICATION DATABASE (notification_db)
    %% ============================================================
    
    NOTIFICATION_TEMPLATES {
        uuid id PK
        varchar template_code UK "NOT NULL"
        varchar template_name "NOT NULL"
        varchar channel "PUSH|EMAIL|SMS"
        varchar event_type "NOT NULL"
        text subject
        text template_body "NOT NULL"
        jsonb variables
        boolean is_active "DEFAULT TRUE"
        timestamp created_at
        varchar created_by
    }
    
    NOTIFICATION_LOGS {
        uuid id PK
        uuid user_id "NOT NULL"
        varchar template_code FK "NOT NULL"
        varchar channel "PUSH|EMAIL|SMS"
        varchar recipient "NOT NULL"
        text subject
        text content "NOT NULL"
        jsonb metadata
        varchar status "PENDING|SENT|DELIVERED|FAILED|BOUNCED"
        varchar provider "FIREBASE|SENDGRID|SMS_GATEWAY"
        varchar provider_message_id
        text error_message
        int retry_count "DEFAULT 0"
        timestamp sent_at
        timestamp delivered_at
        timestamp created_at
    }
    
    USER_NOTIFICATION_PREFERENCES {
        uuid id PK
        uuid user_id "FK, UK, NOT NULL"
        boolean push_enabled "DEFAULT TRUE"
        boolean email_enabled "DEFAULT TRUE"
        boolean sms_enabled "DEFAULT TRUE"
        jsonb channel_preferences
        timestamp created_at
        timestamp updated_at
    }
    
    %% ============================================================
    %% RELATIONSHIPS
    %% ============================================================
    
    %% Auth Domain Relationships
    USERS ||--o{ USER_IDENTITIES : "has multiple identities"
    USERS ||--o{ AUTH_SESSIONS : "has sessions"
    USERS ||--o{ USER_AUDIT_LOGS : "has audit logs"
    USER_IDENTITIES ||--o{ OTP_LOGS : "generates OTP"
    
    %% Ledger Domain Relationships (CORE)
    LEDGER_ACCOUNTS ||--o{ LEDGER_ENTRIES : "records in"
    LEDGER_TRANSACTIONS ||--o{ LEDGER_ENTRIES : "contains"
    LEDGER_TRANSACTIONS ||--o| LEDGER_REVERSAL_LOG : "may be reversed"
    LEDGER_TRANSACTIONS ||--o| LEDGER_REVERSAL_LOG : "reverses original"
    LEDGER_ACCOUNTS ||--o{ LEDGER_ACCOUNT_SNAPSHOTS : "has daily snapshots"
    
    %% Payment Domain Relationships
    USERS ||--o{ DEPOSITS : "initiates"
    USERS ||--o{ WITHDRAWALS : "initiates"
    USERS ||--o{ BANK_ACCOUNTS : "owns"
    WITHDRAWALS }o--|| BANK_ACCOUNTS : "withdraws to"
    PSP_WEBHOOKS }o--|| DEPOSITS : "updates deposit"
    PSP_WEBHOOKS }o--|| WITHDRAWALS : "updates withdrawal"
    
    %% KYC Domain Relationships
    KYC_PROFILES ||--o{ KYC_DOCUMENTS : "has documents"
    KYC_PROFILES ||--o{ KYC_AUDIT_LOGS : "has audit trail"
    
    %% Account Domain Relationships
    CUSTOMERS ||--o{ ACCOUNTS : "owns accounts"
    ACCOUNTS ||--o| ACCOUNT_BALANCES : "has balance"
    ACCOUNTS ||--o{ ACCRUAL_LOGS : "has accrual logs"
    
    %% Portfolio Domain Relationships
    PORTFOLIOS ||--o{ PORTFOLIO_HOLDINGS : "contains holdings"
    PORTFOLIOS ||--o{ ORDERS : "places orders"
    PRODUCTS ||--o{ PORTFOLIO_HOLDINGS : "held in portfolios"
    PRODUCTS ||--o{ ORDERS : "ordered products"
    PARTNERS ||--o{ PRODUCTS : "provides products"
    PARTNERS ||--o{ ORDERS : "executes orders"
    ORDERS ||--o{ ORDER_EXECUTIONS : "has executions"
    
    %% Notification Domain Relationships
    NOTIFICATION_TEMPLATES ||--o{ NOTIFICATION_LOGS : "generates notifications"
    
    %% Cross-Domain Logical Relationships (No FK in DB - Connected via user_id/account_id)
    USERS ||..o| KYC_PROFILES : "has KYC profile"
    USERS ||..o| CUSTOMERS : "becomes customer"
    CUSTOMERS ||..o{ PORTFOLIOS : "manages portfolios"
    USERS ||..o{ NOTIFICATION_LOGS : "receives notifications"
    USERS ||..o| USER_NOTIFICATION_PREFERENCES : "has preferences"
    ACCOUNTS ||..o{ PORTFOLIOS : "funds portfolios"
    DEPOSITS }o..|| LEDGER_TRANSACTIONS : "creates ledger txn"
    WITHDRAWALS }o..|| LEDGER_TRANSACTIONS : "creates ledger txn"
    ACCRUAL_LOGS }o..|| LEDGER_TRANSACTIONS : "posts to ledger"
    ORDERS }o..|| LEDGER_TRANSACTIONS : "creates ledger txn"
```



# Flow luồng nạp tiền

**Luồng hoạt động**:
1. User request deposit
2. Payment Service gọi PSP API
3. PSP trả về payment URL
4. User thanh toán trên bank app
5. PSP gửi webhook SUCCESS → Payment Service
6. Payment Service post ledger entry
7. Account balance được cập nhật (cache)

```mermaid
graph LR
    A[👤 User] -->|1-Init| B[💰 Payment Service]
    B -->|2-Create| C[(💾 deposits table)]
    B -->|3-Call| D[💳 PSP]
    D -->|4-User Pay| E[🏦 Bank App]
    E -->|5-Confirm| D
    D -->|6-Webhook| B
    B -->|7-Post| F[📒 Ledger Service]
    F -->|8-Write| G[(💾 ledger tables)]
    B -->|9-Notify| H[📨 Notification]
    H -->|10-Alert| A
        style G fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
    style F fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
```

**Sequence diagram**
```mermaid
sequenceDiagram
    autonumber
    
    participant User as 👤 User
    participant App as 📱 App
    participant Gateway as 🚪 Gateway
    participant PaymentSvc as 💰 Payment<br/>Service
    participant PSP as 💳 PSP
    participant Bank as 🏦 Bank
    participant LedgerSvc as 📒 Ledger<br/>Service
    participant AccountSvc as 💼 Account<br/>Service
    participant NotifSvc as 📨 Notif
    participant DB as 💾 DB
    
    Note over User,DB: PHASE 1: User Init Deposit
    
    User->>App: Click "Nạp tiền"
    App->>Gateway: POST /deposits/init<br/>{amount: 10000000}<br/>Authorization: Bearer {JWT}
    
    Note over Gateway: JWT Verification (Local)<br/>1. Decode JWT<br/>2. Verify signature (HMAC-SHA256)<br/>3. Check expiration<br/>4. Extract user_id
    
    Gateway->>Gateway: Verify JWT signature ✓<br/>Extract user_id from payload
    Gateway->>PaymentSvc: Forward request<br/>X-User-ID: user-123
    
    PaymentSvc->>DB: INSERT INTO deposits<br/>(status='INIT', amount=10M)
    DB-->>PaymentSvc: deposit_id created
    
    Note over PaymentSvc,PSP: PHASE 2: PSP Integration
    
    PaymentSvc->>PSP: POST /payment/init<br/>{amount: 10M, order_id: deposit_id}
    PSP-->>PaymentSvc: {psp_txn_id, payment_url}
    
    PaymentSvc->>DB: UPDATE deposits<br/>SET status='PENDING',<br/>psp_txn_id, psp_redirect_url
    
    PaymentSvc-->>App: {deposit_id, payment_url, status: "PENDING"}
    
    Note over User,Bank: PHASE 3: User Payment
    
    App->>PSP: Open payment_url (WebView)
    User->>PSP: Choose bank (VCB)
    PSP->>Bank: Deep link: vcbapp://payment
    User->>Bank: Auth & Confirm payment
    Bank->>Bank: Deduct 10M from user account
    Bank->>PSP: Payment SUCCESS
    PSP-->>App: Redirect: wealthhup://deposit-result
    
    Note over PSP,DB: PHASE 4: Webhook Processing
    
    PSP->>PaymentSvc: POST /webhooks/psp<br/>{webhook_id, event: "DEPOSIT_SUCCESS",<br/>txn_id, order_id, status: "SUCCESS"}
    
    PaymentSvc->>PaymentSvc: Verify HMAC signature ✓
    
    PaymentSvc->>DB: SELECT psp_webhooks<br/>WHERE webhook_id = 'WH-123'
    DB-->>PaymentSvc: NOT FOUND (first time)
    
    PaymentSvc->>DB: BEGIN TRANSACTION
    
    PaymentSvc->>DB: INSERT INTO psp_webhooks<br/>(status='PROCESSING')
    
    PaymentSvc->>DB: SELECT * FROM deposits<br/>WHERE id = order_id<br/>FOR UPDATE
    
    PaymentSvc->>DB: UPDATE deposits<br/>SET status='SUCCESS'
    
    Note over PaymentSvc,LedgerSvc: PHASE 5: Ledger Posting
    
    PaymentSvc->>LedgerSvc: POST /internal/ledger/transactions<br/>{type: 'DEPOSIT',<br/>entries: [DR: PLATFORM_CASH, CR: USER_LIAB]}
    
    LedgerSvc->>LedgerSvc: Validate double-entry<br/>Σ DR = Σ CR ✓
    
    LedgerSvc->>DB: INSERT INTO ledger_transactions<br/>(status='POSTED')
    LedgerSvc->>DB: INSERT INTO ledger_entries (2 rows)<br/>DR: PLATFORM_CASH +10M<br/>CR: USER_LIAB +10M
    
    LedgerSvc-->>PaymentSvc: Transaction posted ✓
    
    Note over PaymentSvc,NotifSvc: PHASE 6: Update Cache & Notify
    
    PaymentSvc->>AccountSvc: POST /internal/accounts/refresh-balance
    AccountSvc->>LedgerSvc: GET /internal/ledger/balance
    LedgerSvc-->>AccountSvc: Balance = 10,000,000
    AccountSvc->>DB: UPDATE account_balances<br/>SET ledger_balance=10M
    
    PaymentSvc->>DB: UPDATE psp_webhooks<br/>SET status='PROCESSED'
    
    PaymentSvc->>DB: INSERT INTO user_audit_logs<br/>(event='DEPOSIT_SUCCESS')
    
    PaymentSvc->>DB: COMMIT TRANSACTION
    
    PaymentSvc-->>PSP: 200 OK
    
    PaymentSvc->>NotifSvc: Publish DepositSuccessEvent
    NotifSvc-->>User: 📨 "Nạp tiền thành công 10M"
    
    User->>App: Check balance
    App->>Gateway: GET /accounts/summary
    Gateway->>AccountSvc: Get balance
    AccountSvc->>DB: SELECT account_balances
    DB-->>AccountSvc: available_balance = 10M
    AccountSvc-->>App: Balance data
    App-->>User: Show: "Số dư: 10,000,000 VND" ✅
```

# Flow luồng rút tiền
**Luồng hoạt động**:
1. User request withdrawal
2. Payment Service check balance
3. Payment Service LOCK ledger entry (WITHDRAWAL_PENDING)
4. Payment Service gọi PSP payout API
5. PSP chuyển tiền đến bank của user
6. PSP gửi webhook SUCCESS → Payment Service
7. Payment Service COMPLETE ledger entry (release lock + ghi nhận fee)
8. Account balance được cập nhật (cache)

```mermaid
graph LR
    A[👤 User] -->|1-Init Withdrawal| B[💰 Payment Service]
    B -->|2-Check Balance| C[💼 Account Service]
    C -->|3-Available?| B
    B -->|4-Create| D[(💾 withdrawals table)]
    B -->|5-Lock Balance| E[📒 Ledger Service]
    E -->|6-Write LOCK| F[(💾 ledger tables)]
    B -->|7-Payout Request| G[💳 PSP]
    G -->|8-Transfer| H[🏦 User's Bank]
    H -->|9-Confirm| G
    G -->|10-Webhook| B
    B -->|11-Complete Ledger| E
    E -->|12-Write COMPLETE| F
    B -->|13-Publish Event| I[📮 Kafka]
    I -->|14-Fan-out| J[📨 Notification]
    J -->|15-Alert| A
    
    style E fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
    style F fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
```
**Sequence diagram**:
```mermaid
sequenceDiagram
    autonumber
    
    participant User as 👤 User
    participant App as 📱 App
    participant GW as 🚪 Gateway
    participant PaySvc as 💰 Payment
    participant AccSvc as 💼 Account
    participant LedgerSvc as 📒 Ledger
    participant PSP as 💳 VNPT ePay
    participant Bank as 🏦 Bank
    participant Kafka as 🔥 Kafka
    participant NotifSvc as 📨 Notification
    participant InsightSvc as 📊 Insight
    participant DB as 💾 DB
    
    Note over User,DB: 📍 PHASE 1: Init Withdrawal
    
    User->>App: 1. Click "Rút tiền"
    
    Note over App,GW: Authentication at Gateway (Local JWT Verify)
    App->>GW: 2. GET /accounts/balance<br/>Header: Authorization Bearer JWT
    GW->>GW: 3. Verify JWT signature locally ✓<br/>Extract user_id from JWT payload
    GW->>AccSvc: 4. Get balance (user_id)
    AccSvc->>LedgerSvc: 5. Calculate balance
    LedgerSvc-->>AccSvc: 6. available=10M, locked=0
    AccSvc-->>GW: 7. Balance response
    GW-->>App: 8. Forward response
    App-->>User: 9. Display balance: 10M
    
    User->>App: 10. Input 3,000,000 VND
    User->>App: 11. Choose "Rút nhanh" (IMMEDIATE)
    App-->>User: 12. Confirm: Fee 50k, Net 2.95M
    User->>App: 13. Confirm
    
    Note over App,GW: Authentication & Init Withdrawal
    App->>GW: 14. POST /withdrawals/init<br/>Header: Authorization Bearer JWT<br/>Body: {amount: 3M, speed: IMMEDIATE}
    GW->>GW: 15. Verify JWT signature ✓<br/>Extract user_id, account_id
    GW->>GW: 16. Check rate limit (5/day) ✓
    GW->>PaySvc: 17. Forward with user_id context
    
    Note over PaySvc,LedgerSvc: 📍 PHASE 2: Validate & Lock
    
    PaySvc->>PaySvc: 18. Calculate fee = 50k
    PaySvc->>AccSvc: 19. Check available balance
    AccSvc->>LedgerSvc: 20. Get balance
    LedgerSvc-->>AccSvc: 21. available=10M
    AccSvc-->>PaySvc: 22. Balance sufficient ✓
    
    PaySvc->>DB: 23. BEGIN TRANSACTION
    
    PaySvc->>DB: 24. INSERT withdrawals<br/>(status=PENDING, user_id)
    
    Note over PaySvc,LedgerSvc: 📍 PHASE 3: Ledger Lock (CRITICAL!)
    
    PaySvc->>LedgerSvc: 25. POST /ledger/transactions<br/>{<br/>  type: WITHDRAWAL_LOCK<br/>  entries: [<br/>    DR: WITHDRAWAL_PENDING +3M<br/>    CR: USER_FLEXIBLE_LIAB +3M<br/>  ]<br/>}
    
    LedgerSvc->>LedgerSvc: 26. Validate double-entry<br/>Σ DR = Σ CR ✓
    LedgerSvc->>DB: 27. INSERT ledger_transactions<br/>INSERT ledger_entries (2 rows)
    LedgerSvc-->>PaySvc: 28. Lock posted ✓
    
    PaySvc->>DB: 29. UPDATE withdrawals<br/>SET ledger_lock_txn_id
    
    PaySvc->>AccSvc: 30. Refresh balance
    AccSvc->>DB: 31. UPDATE account_balances<br/>SET available=7M, locked=3M
    
    PaySvc->>DB: 32. COMMIT TRANSACTION
    
    PaySvc-->>GW: 33. Response {<br/>  id, status=PENDING,<br/>  estimated_arrival: "1h"<br/>}
    GW-->>App: 34. Forward
    App-->>User: 35. "Đang xử lý..."
    
    Note over PaySvc,Bank: 📍 PHASE 4: PSP Processing
    
    PaySvc->>DB: 36. SELECT bank_account<br/>WHERE id=bank_account_id
    DB-->>PaySvc: 37. Bank info (encrypted)
    PaySvc->>PaySvc: 38. Decrypt account_number
    
    PaySvc->>PSP: 39. POST /payout/init<br/>{<br/>  amount: 2950000 (net),<br/>  bank_code: VCB,<br/>  account_number: xxx,<br/>  signature: hmac<br/>}
    
    PSP->>PSP: 40. Verify signature ✓
    PSP-->>PaySvc: 41. {<br/>  psp_txn_id: PSP-WD-123,<br/>  status: PROCESSING<br/>}
    
    PaySvc->>DB: 42. UPDATE withdrawals<br/>SET psp_txn_id, status=PROCESSING
    
    PSP->>Bank: 43. Transfer 2.95M to user
    Bank->>Bank: 44. Process transfer
    
    Note over PSP,NotifSvc: 📍 PHASE 5: Success Webhook
    
    Bank-->>PSP: 45. Transfer completed ✓
    
    PSP->>PaySvc: 46. POST /webhooks/psp<br/>{<br/>  webhook_id: WH-456,<br/>  event: WITHDRAWAL_SUCCESS,<br/>  txn_id: PSP-WD-123,<br/>  status: SUCCESS,<br/>  signature: hmac<br/>}
    
    PaySvc->>PaySvc: 47. Verify signature ✓
    PaySvc->>DB: 48. BEGIN TRANSACTION
    
    PaySvc->>DB: 49. Check idempotency<br/>webhook_id exists? NO
    PaySvc->>DB: 50. INSERT psp_webhooks<br/>(status=PROCESSING)
    
    PaySvc->>DB: 51. SELECT withdrawals FOR UPDATE
    DB-->>PaySvc: 52. Withdrawal found, status=PROCESSING
    
    PaySvc->>DB: 53. UPDATE withdrawals<br/>SET status=SUCCESS
    
    Note over PaySvc,LedgerSvc: 📍 PHASE 6: Complete Ledger
    
    PaySvc->>LedgerSvc: 54. POST /ledger/transactions<br/>{<br/>  type: WITHDRAWAL_COMPLETE<br/>  entries: [<br/>    DR: PLATFORM_CASH_CLEARING 2.95M<br/>    CR: WITHDRAWAL_PENDING 2.95M<br/>    DR: WITHDRAWAL_PENDING 50k<br/>    CR: USER_INTEREST_LIAB 50k<br/>    DR: USER_INTEREST_LIAB 50k<br/>    CR: PLATFORM_FEE_REVENUE 50k<br/>  ]<br/>}
    
    LedgerSvc->>LedgerSvc: 55. Validate balance<br/>Σ DR = Σ CR ✓
    LedgerSvc->>DB: 56. INSERT ledger entries
    LedgerSvc-->>PaySvc: 57. Complete posted ✓
    
    PaySvc->>AccSvc: 58. Refresh balance
    AccSvc->>DB: 59. UPDATE account_balances<br/>SET available=7M, locked=0
    
    PaySvc->>DB: 60. UPDATE psp_webhooks<br/>status=PROCESSED
    PaySvc->>DB: 61. COMMIT TRANSACTION
    
    Note over PaySvc,InsightSvc: 📍 PHASE 7: Event Publishing (Kafka)
    
    PaySvc->>Kafka: 62. Publish to "withdrawal-events"<br/>{<br/>  eventType: WithdrawalSuccess,<br/>  userId, withdrawalId, amount<br/>}
    
    par Parallel Consumption
        Kafka->>NotifSvc: 63. Consume (group: notification-group)
        NotifSvc->>NotifSvc: 64. Process event
        NotifSvc-->>User: 65. 📨 Push: "Rút tiền thành công 3M"
        NotifSvc->>DB: 66. INSERT notification_logs
        NotifSvc->>Kafka: 67. Commit offset ✓
    and
        Kafka->>InsightSvc: 68. Consume (group: insight-group)
        InsightSvc->>InsightSvc: 69. Update analytics
        InsightSvc->>DB: 70. INSERT analytics data
        InsightSvc->>Kafka: 71. Commit offset ✓
    end
    
    PaySvc-->>PSP: 72. HTTP 200 OK
    
    Note over User,AccSvc: User Check Balance
    User->>App: 73. Check balance
    App->>GW: 74. GET /accounts/balance<br/>Header: Authorization Bearer JWT
    GW->>GW: 75. Verify JWT locally ✓<br/>Extract user_id
    GW->>AccSvc: 76. Get balance
    AccSvc-->>GW: 77. available=7M
    GW-->>App: 78. Forward response
    App-->>User: 79. ✅ "Số dư: 7,000,000 VND"
    
    Note over User,DB: ✅ HOÀN TẤT: Withdrawal Success
```
# So sánh

```mermaid
graph TB
    subgraph Deposit["📥 DEPOSIT FLOW"]
        D1[User Init] --> D2[Create Record]
        D2 --> D3[Call PSP]
        D3 --> D4[User Pay on Bank]
        D4 --> D5[PSP Success Webhook]
        D5 --> D6[📒 Post to Ledger]
        D6 --> D7[Notify User]
        
        style D6 fill:#51cf66,stroke:#2f9e44,stroke-width:2px
    end
    
    subgraph Withdrawal["📤 WITHDRAWAL FLOW - CRITICAL DIFFERENCE"]
        W1[User Init] --> W2[Check Balance]
        W2 --> W3[Create Record]
        W3 --> W4[📒 LOCK Ledger FIRST!]
        W4 --> W5[Call PSP]
        W5 --> W6[PSP Transfer to Bank]
        W6 --> W7[PSP Success Webhook]
        W7 --> W8[📒 COMPLETE Ledger]
        W8 --> W9[Notify User]
        
        style W4 fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
        style W8 fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
    end
```
