# 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 ```