1. Bối cảnh doanh nghiệp: quy mô, SLA và tuân thủ SBV
Một ngân hàng bán lẻ tại Việt Nam xử lý hàng triệu lệnh chuyển khoản liên ngân hàng mỗi ngày qua NAPAS, cao điểm lên tới vài nghìn giao dịch mỗi giây vào dịp lương và lễ Tết. Mỗi lệnh chạm ít nhất bốn hệ thống: kênh (app/internet banking), core banking, cổng chuyển mạch NAPAS và hệ đối soát cuối ngày. Sai một đồng không chỉ là bug kỹ thuật mà là rủi ro pháp lý, vì Ngân hàng Nhà nước (SBV) và chuẩn NAPAS bắt buộc đối soát khớp tuyệt đối. Đây là lý do ta không kiểm thử bằng cách nhìn màn hình báo 'thành công', mà kiểm thử bằng bất biến nghiệp vụ.
SLA của luồng này rất khắt khe: chuyển nhanh 24/7 phải phản hồi dưới năm giây, tỷ lệ thành công trên 99,9%, và mọi lệnh chưa rõ kết quả phải được xử lý dứt điểm trước khi chốt sổ. Đội QA truyền thống không thể tự tay dựng đủ tổ hợp ca — nghẽn mạng, timeout, gửi trùng, đảo chiều bù trừ, lệch tỷ giá làm tròn. Vì vậy chúng tôi bổ sung một AI test agent: nó khám phá không gian ca kiểm thử, tự sinh và chạy ca trong sandbox, còn con người giữ vai trò định nghĩa oracle và phê duyệt ca rủi ro.
- Quy mô: hàng triệu giao dịch/ngày, đỉnh vài nghìn TPS.
- SLA: phản hồi < 5s, thành công > 99,9%, xử lý dứt điểm trước chốt sổ.
- Tuân thủ: đối soát NAPAS khớp tuyệt đối theo yêu cầu SBV.
2. Kiến trúc & luồng dữ liệu của agent
Agent tuân theo mô hình ba tác nhân cộng tác giống Playwright Agents: Planner khám phá ứng dụng và viết kế hoạch kiểm thử dạng Markdown; Generator biến kế hoạch thành spec chạy được, xác thực locator/endpoint trên hệ thống sandbox thật; Healer chạy khi có lỗi, đọc console/network/trace và tự sửa hoặc đánh dấu skip. Điểm mấu chốt của phiên bản ngân hàng là mọi tác nhân đều bị chặn sau một lớp guardrail: chúng chỉ được gọi API sandbox và mock NAPAS, không bao giờ chạm cổng thanh toán thật.
Luồng dữ liệu bắt đầu từ một 'ý định chuyển tiền' (tài khoản nguồn, tài khoản đích, số tiền, mã tham chiếu). Planner sinh các biến thể ý định; Generator dịch mỗi ý định thành lời gọi API và bút toán kỳ vọng; sau khi chạy, kết quả được đưa qua tầng oracle để so với bất biến. Mọi bước đều ghi trace để có thể phát lại tất định (deterministic replay) — điều bắt buộc trong môi trường tài chính vì một ca lỗi phải tái hiện được y hệt khi điều tra.
// transfer intent — đơn vị dữ liệu agent thao tác
export interface TransferIntent {
fromAccount: string; // IBAN/số TK nguồn
toBank: string; // mã BIN ngân hàng đích (NAPAS)
toAccount: string;
amountMinor: bigint; // đơn vị nhỏ nhất (VND không có phần lẻ)
currency: "VND";
refId: string; // idempotency key duy nhất từ kênh
channel: "MOBILE" | "IB" | "BATCH";
}
// bút toán kép kỳ vọng cho một lệnh SETTLED
export interface ExpectedLedger {
debit: { account: string; amountMinor: bigint };
credit: { account: string; amountMinor: bigint };
fee?: { account: string; amountMinor: bigint };
}3. Mô hình dữ liệu: ledger, lệnh và đối soát
Trái tim của bài toán là sổ cái bút toán kép. Mỗi lệnh chuyển tạo ít nhất hai dòng ledger: ghi nợ tài khoản nguồn và ghi có tài khoản đích (cộng phí nếu có, ghi có tài khoản thu phí). Tổng ghi nợ luôn bằng tổng ghi có trong một giao dịch — đó là bất biến bảo toàn tiền. Bên cạnh ledger, ta có bảng lệnh (transfer_order) lưu trạng thái máy, và bảng đối soát (recon_line) khớp từng lệnh với bản ghi trả về từ NAPAS.
-- Sổ cái bút toán kép (append-only, không UPDATE)
CREATE TABLE ledger_entry (
id BIGSERIAL PRIMARY KEY,
txn_id UUID NOT NULL, -- gom các dòng của 1 giao dịch
account TEXT NOT NULL,
direction CHAR(1) NOT NULL CHECK (direction IN ('D','C')), -- Debit/Credit
amount_minor BIGINT NOT NULL CHECK (amount_minor > 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Lệnh chuyển + khoá idempotency
CREATE TABLE transfer_order (
ref_id TEXT PRIMARY KEY, -- idempotency key từ kênh
state TEXT NOT NULL, -- INIT|PENDING|SETTLED|REVERSED|RECONCILED
amount_minor BIGINT NOT NULL,
napas_trace TEXT, -- trace số từ switch
UNIQUE (ref_id) -- không cho tạo trùng
);
-- Dòng đối soát NAPAS
CREATE TABLE recon_line (
napas_trace TEXT PRIMARY KEY,
ref_id TEXT REFERENCES transfer_order(ref_id),
amount_minor BIGINT NOT NULL,
matched BOOLEAN NOT NULL DEFAULT false
);
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!