Xây dựng một Hệ thống Core Banking (CBS) cho một Tổ chức Tài chính Vi mô (MFI - Microfinance Institution) mang lại một tập hợp các thách thức kỹ thuật hoàn toàn khác biệt so với ngân hàng bán lẻ truyền thống. Trong khi các ngân hàng thương mại tập trung chủ yếu vào điểm tín dụng cá nhân và mạng lưới thẻ, tài chính vi mô lại vận hành dựa trên các giao dịch giá trị thấp với tần suất cao, cho vay theo nhóm (group-based lending), và thu nợ thực địa ngoại tuyến (offline field collections).

Nếu bạn là một kỹ sư hoặc Business Analyst đang chuyển dịch sang mảng fintech, hiểu được các sắc thái kiến trúc của các nền tảng như Apache Fineract (Mifos X) hay Musoni là cực kỳ quan trọng. Trong hướng dẫn này, chúng ta sẽ phân tích 5 mô-đun bắt buộc phải có của một hệ thống Core Banking Tài chính Vi mô, cung cấp các cấu trúc database, công thức toán học, ánh xạ kế toán kép (double-entry mapping) và các đoạn PRD (Product Requirements Document) thực tế mà bạn cần để xây dựng chúng.


1. Sự Khác biệt Cơ bản: Trách nhiệm Liên đới (Joint Liability)

Nền tảng của tài chính vi mô là Nhóm trách nhiệm liên đới (JLG - Joint Liability Group). Vì người đi vay thường thiếu tài sản thế chấp truyền thống, họ được nhóm lại thành các “tổ/nhóm” từ 5 đến 10 cá nhân để bảo lãnh chéo cho các khoản vay của nhau. Nếu một thành viên vỡ nợ, cả nhóm sẽ phải chịu trách nhiệm và khoản tiền tiết kiệm tập thể của họ sẽ bị khóa. Điều này làm thay đổi đáng kể cách thiết kế CIF (Customer Information File) và công cụ tính toán Khoản vay (Loan Engine).

Mô-đun 1: CIF, KYC và Nhóm trách nhiệm liên đới (JLGs)

Trong kiến trúc JLG, hệ thống phải theo dõi lịch trả nợ của từng cá nhân nhưng đồng thời cho phép các luồng công việc quản trị ở cấp độ nhóm.

Thiết kế Schema Database:

  • m_group: Đại diện cho nhóm vật lý (ngày họp nhóm, cán bộ tín dụng phụ trách).
  • m_client: Đại diện cho cá nhân người đi vay.
  • m_loan: Lưu trữ chi tiết khoản vay. Đối với các khoản vay JLG, trường loan_type_enum được đặt thành JLG, và cả hai trường client_idgroup_id đều được ghi nhận dữ liệu. Điều này tránh được mẫu thiết kế phản diện (anti-pattern) “khoản vay nhóm nguyên khối” và giúp theo dõi được chi tiết từng cá nhân.

User Story: Là một Cán bộ Tín dụng, tôi muốn liên kết nhiều hồ sơ người vay cá nhân vào một Nhóm trách nhiệm liên đới (JLG) duy nhất, để hệ thống có thể tự động áp dụng bảo lãnh chéo tập thể và theo dõi rủi ro ở cấp độ nhóm.

Tiêu chí Chấp nhận (Acceptance Criteria):

  • Given (Bối cảnh): Người vay A, B, và C được liên kết chung dưới JLG “Tổ 1”.
  • When (Hành động): Người vay A trễ hạn trả nợ gốc quá 30 ngày.
  • Then (Kết quả): Hệ thống phải tự động chuyển trạng thái của JLG sang “Quá hạn (Delinquent)”.
  • And (Và): Kích hoạt lệnh khóa rút tiền tạm thời trên tài khoản tiết kiệm liên kết của các thành viên bảo lãnh chéo B và C.

2. Mô-đun Quản lý Khoản vay và Công cụ Tính Lịch trả nợ (Amortization Engine)

Công cụ tín dụng xử lý các phép tính toán học nặng nề. Các MFI thường sử dụng hai phương pháp tính lãi chính, các phương pháp này phải hoàn toàn bất biến (immutable) một khi khoản vay đã được giải ngân hoạt động.

  1. Flat Rate (Lãi suất phẳng/Lãi đầu kỳ): Tiền lãi được tính một lần duy nhất trên số dư nợ gốc ban đầu và giữ cố định cho mọi kỳ hạn thanh toán.
  2. Declining Balance (Lãi suất giảm dần): Tiền lãi được tính theo định kỳ dựa trên số dư nợ gốc thực tế còn lại. Công thức tính niên kim (annuity formula) tiêu chuẩn cho Số tiền thanh toán hàng tháng bằng nhau (EMI - Equated Monthly Installment) là: $$EMI = \frac{i \times P}{1 - (1 + i)^{-n}}$$ (Trong đó $i$ là lãi suất mỗi kỳ, $P$ là dư nợ gốc còn lại, và $n$ là tổng số kỳ hạn thanh toán còn lại).

User Story: Là Quản trị viên Hệ thống, tôi muốn cấu hình một sản phẩm vay theo phương pháp Dư nợ giảm dần với Kỳ thanh toán bằng nhau (EMI), để tiền lãi được tính toán một cách chính xác chỉ dựa trên dư nợ gốc còn lại.

Tiêu chí Chấp nhận (Logic phân bổ thanh toán):

  • Given: Một khoản vay đang hoạt động có các khoản phải trả hiện tại gồm: 10$ tiền phạt (penalties), 5$ tiền phí (fees), 20$ tiền lãi (interest) và 100$ tiền gốc (principal).
  • When: Người vay thực hiện thanh toán một phần với số tiền 25$.
  • Then: Hệ thống trước tiên phải khấu trừ 10$ tiền phạt và 5$ tiền phí (thứ tự phân bổ nghiêm ngặt: Phạt $\rightarrow$ Phí $\rightarrow$ Lãi $\rightarrow$ Gốc).
  • And: Phân bổ 10$ còn lại vào tiền lãi.
  • And: Cập nhật số dư nợ còn lại là 10$ tiền lãi và 100$ tiền gốc phải trả.

3. Mô-đun CASA và Logic Tiết kiệm Bắt buộc (Compulsory Savings)

Để giảm thiểu rủi ro, các tổ chức tài chính vi mô sử dụng Tiết kiệm Bắt buộc (Compulsory Savings) làm tài sản thế chấp bằng tiền mặt. Hệ thống CBS phải tạo ra cầu nối liền mạch giữa mô-đun CASA (Current Account, Savings Account) và mô-đun Khoản vay (Loan).

Hệ thống sẽ áp đặt một ràng buộc khóa quỹ HOLD_FUNDS trên tài khoản tiết kiệm liên kết của khách hàng tương đương với tỷ lệ ký quỹ bắt buộc của sản phẩm (ví dụ: 20% giá trị khoản vay được giải ngân). Khi khoản vay được trả dần, hệ thống phải thực thi các lệnh giải tỏa quỹ RELEASE_FUNDS tương ứng theo tỷ lệ.

User Story: Là một Quản lý Rủi ro, tôi muốn hệ thống tự động khóa một tỷ lệ phần trăm xác định của khoản vay giải ngân làm tiền tiết kiệm bắt buộc, để tổ chức có sẵn tài sản thế chấp bằng tiền mặt chống lại rủi ro nợ xấu.

Tiêu chí Chấp nhận:

  • Given: Người vay có khoản vay đang hoạt động trị giá 1.000$ với yêu cầu ký quỹ 20% (200$ hiện đang bị khóa trong tài khoản tiết kiệm qua hàm enforceMinRequiredBalance).
  • When: Người vay thanh toán được 500$ tiền gốc của khoản vay.
  • Then: Hệ thống phải tính toán lại số tiền cần khóa là 100$ (20% của 500$).
  • And: Giải tỏa 100$ trong tài khoản tiết kiệm, giúp số tiền này có thể rút ra ngay lập tức.

4. Giao dịch Quầy, Két tiền và Hạch toán theo lô (Batch Repayments)

Vì các giao dịch thường diễn ra ngoại tuyến tại các buổi họp thôn/bản và được mang về chi nhánh sau đó, việc xử lý tiền mặt theo lô lớn (high-volume cash batching) là cực kỳ quan trọng. Cấp bậc quỹ tiền mặt vật lý được ánh xạ trực tiếp vào Sổ Cái: Két Chính (GL: 1001) $\rightarrow$ Két Chi Nhánh (GL: 1002) $\rightarrow$ Quầy Giao Dịch Viên (GL: 1003).

Để xử lý một buổi thu nợ, CBS cung cấp Collection Sheets (Bảng thu tiền) — một payload API duy nhất chứa thông tin thanh toán nợ và gửi tiết kiệm của hơn 20 thành viên trong nhóm cùng một lúc.

User Story: Là một Giao dịch viên, tôi muốn sử dụng Bảng thu tiền để xử lý thanh toán cho cả buổi họp tổ trong một giao dịch duy nhất, để có thể xử lý hiệu quả lượng giao dịch tiền mặt lớn mà không cần nhập thủ công cho từng cá nhân.

Tiêu chí Chấp nhận:

  • Given: Giao dịch viên gửi một Bảng thu tiền chứa thông tin thanh toán của 20 thành viên JLG.
  • When: Dữ liệu thanh toán của thành viên thứ 15 gặp lỗi xác thực hệ thống.
  • Then: Hệ thống phải từ chối toàn bộ Bảng thu tiền đó.
  • And: Rollback tất cả các giao dịch sổ cái của 19 thành viên còn lại (đảm bảo tính nguyên tử ACID của giao dịch).
  • And: Ghi lại log lỗi chi tiết tại dòng bị lỗi để giao dịch viên sửa chữa.

5. Sổ Cái Tổng Hợp và Động cơ Kế toán Kép (Double-Entry Engine)

Mỗi sự kiện trong vòng đời tài chính phải hạch toán một bút toán kép nghiêm ngặt vào bảng acc_gl_journal_entry, giữ cho các tài khoản Tài sản (Assets), Nợ phải trả (Liabilities), Vốn chủ sở hữu (Equity), Doanh thu (Revenues) và Chi phí (Expenses) luôn cân bằng.

Sự kiện Vòng đờiTài khoản ghi Nợ (Debit)Tài khoản ghi Có (Credit)Loại Tài khoản (Dr / Cr)
Giải ngânKhoản Phải Thu Cho VayTiền mặt / Ngân hàngTài sản (+) / Tài sản (-)
Thu nợ (Thông thường)Tiền mặt / Ngân hàngKhoản Phải Thu / Doanh thu LãiTài sản (+) / Tài sản (-) / Doanh thu (+)
Trích trước LãiLãi Phải ThuDoanh thu LãiTài sản (+) / Doanh thu (+)
Xóa nợ xấu (NPA)Dự phòng Tổn thất Cho vayKhoản Phải Thu Cho VayTài sản tương phản (+) / Tài sản (-)

User Story: Là một Quản lý Tài chính, tôi muốn mọi sự kiện vòng đời core banking tự động hạch toán bút toán kép, để Sổ Cái Tổng Hợp (GL) luôn được cân bằng theo thời gian thực.

Tiêu chí Chấp nhận:

  • Given: Batch chạy cuối ngày (EOD) kích hoạt chuỗi Trích trước lãi.
  • When: Chính xác 5,00$ tiền lãi được trích trước trên một khoản vay đang hoạt động.
  • Then: Hệ thống phải tạo ra một bút toán không thể sửa đổi với 5,00$ ghi Nợ vào “Lãi Phải Thu” và 5,00$ ghi Có vào “Doanh thu Lãi”.

Trọng tâm QA: Kiểm thử các Máy Trạng thái và Job EOD

Khi kiểm thử một hệ thống CBS, các kỹ sư QA phải xác thực nghiêm ngặt hai khu vực cốt lõi: Máy trạng thái (State Machine) và chuỗi Batch cuối ngày (EOD).

Máy Trạng thái Vòng đời Khoản vay

Tương tự như cách chúng ta khám phá logic máy trạng thái trong các nền tảng TMĐT phức tạp, một tài khoản vay dựa trên các lệnh chuyển đổi trạng thái nghiêm ngặt (approve, disburse, undo-disbursal, write-off).

stateDiagram-v2
    [*] --> Submitted: Tạo Hồ sơ Vay
    Submitted --> Approved: Phê duyệt Tín dụng
    Submitted --> Rejected: Từ chối Hồ sơ
    Approved --> Active: Giải ngân Quỹ
    Approved --> Withdrawn: Khách hàng Rút hồ sơ
    Active --> Closed_Paid: Đã Tất toán (Hoàn thành Nghĩa vụ)
    Active --> Delinquent: Quá hạn > 30 Ngày
    Delinquent --> Closed_Paid: Thanh toán Đầy đủ
    Delinquent --> Closed_WrittenOff: Xóa nợ xấu (NPA)

Kịch bản QA: Nếu một lệnh gọi API cố gắng giải ngân (Disburse) một khoản vay đang ở trạng thái Submitted, hệ thống phải chặn thao tác này. Khoản vay bắt buộc phải được chuyển sang trạng thái Approved trước.

Khả năng Chống chịu của Batch xử lý cuối ngày (EOD)

Quy trình khóa sổ ngày làm việc (COB - Close of Business) chạy một chuỗi tuần tự nghiêm ngặt: Hạch toán giao dịch $\rightarrow$ Trích trước lãi $\rightarrow$ Tính phạt chậm trả $\rightarrow$ Đối soát $\rightarrow$ Khóa ngày.

Vì các kiến trúc CBS hiện đại quản lý hàng triệu khoản vay, chúng phụ thuộc vào xử lý phân tán (như Spring Batch Remote Partitioning). Node quản lý (manager node) chia nhỏ dữ liệu, và các node worker xử lý chúng song song. Thay vì các khung giờ chạy batch nguyên khối gây dừng hệ thống, chúng ta ngày càng thấy các ngân hàng chuyển sang xử lý các tác vụ EOD với kiến trúc hướng sự kiện để giảm thiểu tối đa downtime.

Kịch bản QA: Nếu một job EOD xử lý 10.000 khoản vay và khoản vay số #5045 gặp lỗi làm tròn số thực, hệ thống phải tự động cách ly khoản vay #5045 vào một Hàng đợi Thư chết (DLQ - Dead Letter Queue) và tiếp tục hoàn tất thành công 9.999 khoản vay còn lại mà không làm dừng toàn bộ tiến trình batch của ngày.


Bài viết này nằm trong series core-banking-developer khám phá các thiết kế kiến trúc fintech có khả năng mở rộng cao.


🤝 Kết nối với tôi

Bạn đang gặp phải những thách thức tương tự về kiến trúc hệ thống, mở rộng quy mô (scaling) hay dịch chuyển (migration)? Hãy kết nối với tôi trên LinkedIn, theo dõi GitHub của tôi, hoặc gửi một email để trao đổi nhé.