SDK MONA Pay: Node.js, Python, PHP, Go và các ngôn ngữ khác
Video hướng dẫn
Xem transcript video (8 phần)
SDK cho Node TypeScript
Bạn có một dự án Node.js hoặc TypeScript và muốn thu tiền chuyển khoản mà không tự viết từng lệnh HTTP. Trong mười phút, mình sẽ cài SDK chính thức, khởi tạo client bằng biến môi trường, tạo link thu tiền, đi qua luồng tạo tài khoản ảo, tạo VietQR và xác thực webhook. SDK có tên @monapay/node, không có dependency và dùng fetch có sẵn của Node mười tám trở lên.
Cài và tạo client
Cài gói bằng npm install @monapay/node. Sau đó đặt MONAPAY_CLIENT_ID và MONAPAY_CLIENT_SECRET lấy từ API Keys trên my.monapay.vn. Trong code, import MonaPay rồi gọi MonaPay.fromEnv(). Lệnh đầu tiên tự lấy OAuth token; các lệnh sau dùng lại token tới gần hạn. SDK làm mới sớm sáu mươi giây và thử lại đúng một lần khi gặp HTTP bốn trăm lẻ một. Secret phải nằm trong biến môi trường, không commit lên git.
Tạo link thu tiền
Để tạo link thu tiền, gọi mona.checkouts.create với amount, order_code và return_url. Kết quả trả checkout_url; bạn chuyển hướng khách tới đó hoặc gửi link qua tin nhắn. Theo tài liệu API, mỗi đơn nên có mã riêng, request tạo checkout cần khóa chống tạo trùng, và đơn chỉ được xác nhận sau sự kiện CHECKOUT_PAID đã kiểm chữ ký hoặc sau khi đọc lại checkout thấy status là paid.
Tạo VA qua hai OTP
Luồng tạo tài khoản ảo có bốn bước và hai OTP. Gọi registerVirtualAccount với loại khách hàng, số tài khoản, số điện thoại, đầu số VA, nội dung định danh và user_agreement. Lấy registration.acb_request.id để gọi verifyVirtualAccount bằng OTP người dùng nhập. Sau đó gọi registerNotification với ID của VA, nhận OTP lần hai và gọi verifyNotification. Bỏ hai bước thông báo thì VA tồn tại nhưng webhook giao dịch sẽ không chạy.
Tạo VietQR động
Khi đã có thông tin ACB, dùng mona.qr.generate. Các trường chính gồm ownerNumber, ownerType, merchantId, terminalId, orderId, virtualAccountPrefix, beneficiaryName, amount và description. Các giá trị merchant và terminal đúng được xem trong dashboard tại mục Tạo QR. SDK trả dữ liệu trực tiếp trong response, nên đọc qr.qr_data_url. Đây là payload VietQR để đưa vào thư viện vẽ QR, không phải URL ảnh. Lưu ID và số VA đầy đủ để hủy hoặc đối chiếu khi cần.
Kiểm chữ ký webhook
Phần dễ sai nhất là webhook. Route phải nhận raw body trước khi middleware phân tích JSON. Truyền đúng bytes cùng headers và MONA_WEBHOOK_SECRET vào verifyWebhook. Chỉ xử lý khi result.ok là true, rồi lưu transaction_code bằng unique constraint để một giao dịch gửi lại không cập nhật đơn hai lần. Sự kiện checkout dùng checkout_id, không dùng id. Ví dụ Express hoàn chỉnh và ví dụ Next.js App Router đều có trong README.
Chạy thử bằng sandbox
Trước khi nối ngân hàng, bạn có thể thử cả luồng bằng sandbox. Tạo checkout với sandbox: true, sau đó gọi mona.sandbox.transaction bằng số VA của checkout, đúng số tiền và mã đơn. Đọc lại checkout để thấy trạng thái paid. Sandbox vẫn phát webhook và chạy bộ khớp checkout nhưng không chuyển tiền. Ca thiếu tiền phải giữ status là pending; chỉ giao hàng khi đã nhận đủ và xác minh thành công.
Sửa lỗi và xem tài liệu
Nếu import lỗi, kiểm tra dự án đang dùng Node từ phiên bản mười tám và đúng tên gói có scope là @monapay/node. Nếu API lỗi, MonaPayError có status và body để log. Các method trả trực tiếp trường data của response. Nếu chữ ký sai, đừng stringify lại object đã parse; hãy lấy nguyên raw body. README, danh sách method và ví dụ nằm trên npm, còn đặc tả endpoint ở monapay.vn/docs. Đăng ký tại my.monapay.vn hoặc gọi một chín không không, sáu ba sáu, sáu bốn tám.
Xem transcript video (8 phần)
SDK cho Python backend
Bạn có dự án Flask, FastAPI hoặc Django và muốn tạo link chuyển khoản, nhận kết quả thanh toán mà không tự ghép từng request. Trong video này, mình cài SDK Python chính thức, tạo client, tạo checkout, chạy sandbox và kiểm chữ ký webhook. Tên gói trên PyPI là monapay. SDK chạy từ Python ba chấm tám, dùng đồng bộ và chỉ dựa vào thư viện chuẩn.
Cài gói từ PyPI
Cài gói bằng pip install monapay. Tạo API key tại my.monapay.vn rồi đặt MONAPAY_CLIENT_ID và MONAPAY_CLIENT_SECRET trong môi trường chạy ứng dụng. Import MonaPay từ monapay và gọi MonaPay.from_env(). Client tự lấy OAuth token, lưu tới gần hạn, làm mới sớm sáu mươi giây và thử lại đúng một lần khi gặp HTTP bốn trăm lẻ một. Không ghi client secret trực tiếp trong repository.
Tạo client từ môi trường
Python SDK đặt tên nhóm theo kiểu snake case: bank_accounts, webhook_logs, email_configs, email_logs và payment_profile. Các method trả trực tiếp trường data của response, nên không cần bóc thêm lớp success, message, data trong code sử dụng. Khi cần truyền thông tin tường minh, khởi tạo MonaPay với client_id và client_secret. Lỗi API là ApiError, có status và body để ghi log và tìm trường sai. Alias paymentProfile cũng có sẵn nếu bạn muốn giữ cách gọi giống SDK Node.
Tạo link thu tiền
Tạo link thu tiền bằng mona.checkouts.create. Body cần amount, order_code và return_url; kết quả cho checkout_url để chuyển hướng hoặc gửi khách. Mỗi phiên mặc định hết hạn sau chín trăm giây theo tài liệu API. Hệ thống chỉ đánh dấu paid khi tổng tiền nhận lớn hơn hoặc bằng số tiền checkout. Vì vậy, ứng dụng không được xác nhận đơn chỉ vì khách quay về return_url.
Thử checkout sandbox
Muốn kiểm luồng mà chưa nối ngân hàng, tạo checkout với sandbox=True. Lấy checkout["bank"]["account_number"], số tiền và mã đơn để gọi mona.sandbox.transaction. Sau đó đọc lại checkout bằng mona.checkouts.get. Trạng thái đủ tiền sẽ là paid; thiếu tiền vẫn là pending. Sandbox phát webhook như luồng thật nhưng không chuyển tiền, rất phù hợp để thử mapping đơn và chống xử lý trùng trước khi bật production.
Xác thực webhook
Để xác thực webhook, import verify_webhook. Truyền raw_body, headers và MONA_WEBHOOK_SECRET; raw body phải là bytes nguyên bản từ request. Các framework phải giữ body trước mọi middleware phân tích JSON. Nếu result.ok không đúng, trả HTTP bốn trăm lẻ một với result.reason. Nếu hợp lệ, đọc payload và lưu transaction_code bằng unique key trước khi đổi trạng thái đơn. Không parse JSON rồi encode lại, vì chỉ một khác biệt byte cũng làm chữ ký không khớp.
Chặn lỗi xử lý đơn
Với sự kiện CHECKOUT_PAID, trường định danh phiên là checkout_id, không phải id. Sau khi kiểm chữ ký, lấy checkout từ API, so order_code, số tiền và trạng thái paid rồi mới giao hàng. Flask, FastAPI và Django đều có ví dụ trong thư mục examples. Nếu cần đọc nhiều giao dịch, dùng iterator và vẫn chống trùng bằng transaction_code. Nhớ dùng tên Python from_env, không dùng fromEnv của Node. Đây là lỗi đặt tên thường gặp khi chuyển ví dụ giữa hai SDK.
Sửa lỗi và xem docs
Nếu pip cài nhầm gói, đối chiếu trang PyPI có tên đúng monapay và phiên bản Python từ ba chấm tám. Nếu nhận HTTP bốn trăm lẻ hai, đọc ApiError.body để sửa trường hoặc kiểu dữ liệu. Nếu webhook luôn sai, kiểm raw bytes, secret và headers trước. README cùng code mẫu nằm trên PyPI; bản này cũng chỉ rõ lệnh chạy bộ test bằng pytest. Đặc tả checkout ở monapay.vn/docs/api/trang-thanh-toan. Bạn đăng ký tại my.monapay.vn hoặc gọi một chín không không, sáu ba sáu, sáu bốn tám.
MONA Pay có SDK cho 10 hệ sinh thái. Node.js và Python đã có trên npm, PyPI; Go cài trực tiếp bằng module đã gắn tag. Các SDK còn lại có source và tag trên GitHub nhưng chưa có trên registry tương ứng.
Nếu bạn đang bắt đầu một web JavaScript hoặc TypeScript, dùng Node.js. Flask, FastAPI hoặc Django dùng Python. Dự án đã chạy ngôn ngữ nào thì chọn SDK của ngôn ngữ đó để giữ secret ở backend.
Bảng package và trạng thái phát hành
| Ngôn ngữ | Package hoặc module | Lệnh cài | Trạng thái trong repo |
|---|---|---|---|
| Node.js / TypeScript | @monapay/node 0.5.1 |
npm install @monapay/node |
Đã publish trên npm |
| Python | monapay 0.5.1 |
pip install monapay |
Đã publish trên PyPI |
| Go | github.com/themonagroup/monapay-go 0.4.0 |
go get github.com/themonagroup/[email protected] |
Đã có repo và tag công khai |
| PHP | monapay/php-sdk 0.4.0 |
Sau khi lên Packagist: composer require monapay/php-sdk |
Trên GitHub, chưa lên registry |
| Java | com.themona:monapay 0.4.0 |
Thêm dependency Maven ghi trong README sau khi lên Central | Trên GitHub, chưa lên registry |
| .NET | MonaPay 0.4.0 |
Sau khi lên NuGet: dotnet add package MonaPay --version 0.4.0 |
Trên GitHub, chưa lên registry |
| Ruby | gem monapay 0.4.0 |
Sau khi lên RubyGems: gem "monapay", "~> 0.4" |
Trên GitHub, chưa lên registry |
| Dart / Flutter | package monapay 0.4.0 |
Dùng source từ repo hiện tại | Trên GitHub, chưa lên registry |
| Rust | crate monapay 0.4.0 |
Dùng source từ repo hiện tại | Trên GitHub, chưa lên registry |
| Elixir | Hex package monapay 0.4.0 |
Dùng source từ repo hiện tại | Trên GitHub, chưa lên registry |
Không chạy lệnh cài từ registry cho 7 dòng ghi “chưa lên registry”. Mở GitHub The MONA Group, chọn repo monapay-<ngôn-ngữ>, rồi làm theo README và tag trong repo. Trang Mã nguồn MONA Pay trên GitHub có danh sách link đầy đủ.
Cách xác thực chung
Base URL production là:
Vào dashboard my.monapay.vn, mở API Keys, tạo một key riêng cho ứng dụng và lưu hai giá trị sau vào secret store của backend:
SDK đổi client_id và client_secret thành Bearer token qua POST /api/v1/oauth/token, cache token tới gần hạn và lấy lại một lần khi gặp HTTP 401. Các SDK tự gắn X-Client-Secret cho lệnh ghi POST, PUT và DELETE.
MONAPAY_CLIENT_SECRET dùng để ứng dụng gọi MONA Pay. MONAPAY_WEBHOOK_SECRET dùng để kiểm chữ ký dữ liệu MONA Pay gửi về. Hãy dùng hai chuỗi khác nhau và không đưa chúng xuống trình duyệt.
Node.js: khởi tạo client
Node.js SDK cần Node 18 trở lên vì dùng fetch có sẵn:
Nếu đã đặt đủ biến môi trường, có thể viết gọn:
1. Tạo checkout
Đưa checkout_url cho khách hoặc chuyển hướng trình duyệt sang đó. Chỉ giao hàng khi webhook CHECKOUT_PAID đã qua bước kiểm chữ ký và khớp đơn, số tiền; redirect của trình duyệt không đủ để xác nhận thanh toán.
2. Tra giao dịch theo tài khoản ảo
transaction_code không đổi qua các lần gửi lại. Lưu trường này bằng unique constraint để một giao dịch chỉ cập nhật đơn một lần.
3. Kiểm chữ ký webhook HMAC
Phải truyền đúng raw body nhận từ mạng. Nếu parse JSON rồi stringify lại, byte có thể đổi và chữ ký sẽ sai. Verifier kiểm HMAC-SHA256, so sánh constant-time và mặc định từ chối timestamp lệch quá 300 giây.
Python: khởi tạo client
Python SDK đồng bộ và chỉ dùng thư viện chuẩn:
Khi đã đặt biến môi trường, dùng MonaPay.from_env().
1. Tạo checkout
Webhook CHECKOUT_PAID dùng trường checkout_id. Sau khi kiểm chữ ký, gọi mona.checkouts.get(event["checkout_id"]) để đối chiếu trạng thái server-side trước khi giao hàng.
2. Tra giao dịch theo tài khoản ảo
Muốn đọc hết nhiều trang, dùng mona.iter_transactions("MONA0000010234", limit=100).
3. Kiểm chữ ký webhook HMAC
raw_body phải là bytes. SDK kiểm hai header X-Mona-Timestamp, X-Mona-Signature và cửa sổ 300 giây trước khi parse JSON.
Thử luồng thanh toán bằng sandbox
Sandbox tạo giao dịch giả, bắn webhook và khớp checkout như luồng thật nhưng không chuyển tiền, không tính hạn mức. Tài khoản chưa nối ngân hàng vẫn dùng được.
Node.js
Python
Chạy sandbox trước với đủ tiền, thiếu tiền, chữ ký sai và transaction_code lặp. Khi lên production, giữ endpoint webhook public qua HTTPS, trả HTTP 200, 201 hoặc 202 trong 10 giây rồi xử lý phần nặng ở hàng đợi.
Đọc thêm: Sandbox · Bảo mật webhook · API reference