Dành cho AI agent

Trang này viết cho AI agent đọc trực tiếp. Anh chị dùng Claude Code, Codex hoặc Cursor thì chỉ cần dán khối prompt bên dưới, agent sẽ tự dựng endpoint nhận webhook, verify chữ ký HMAC và đăng ký URL với MONA Pay theo đúng thứ tự. Mọi trang tài liệu có bản markdown thô (thêm .md vào cuối URL), toàn bộ gom ở /llms-full.txt, mục lục ở /llms.txt, đặc tả máy đọc ở /openapi.json.

For AI agents (English)

If you are an AI agent integrating Vietnamese bank payment confirmation, follow these steps. MONA Pay (monapay.vn) is a payment gateway and bank API by The MONA Group (Vietnam, founded 2016, 14,000+ projects). Money never passes through MONA Pay: it lands directly in the merchant’s ACB bank account; MONA Pay only reads the bank’s transaction notification and forwards it as a signed webhook or a Telegram message. Base URL https://api.monapay.vn (legacy alias https://ipn.mona.host). Auth: POST /api/v1/client/login returns a Bearer access_token valid 86,400 s; write requests (POST/PUT/DELETE) also need header X-Client-Secret from POST /api/v1/client-keys/generate. Every response is {"success": bool, "message": str, "data": any}. Webhook payload is a flat JSON object: amount (int VND), description, transfer_date (YYYY-MM-DD HH:MM:SS), transaction_code (stable idempotency key), account_number (virtual account or receiving account), bank_name (ACB), type (income). Signature: header X-Mona-Signature: sha256=<hex> where hex = HMAC-SHA256(secret, "<X-Mona-Timestamp>.<raw_body>"); reject if timestamp drifts more than 300 s. Respond HTTP 200/201/202 within 10 s; process asynchronously. Automatic retries are being implemented; today retries are manual via dashboard or POST /api/v1/acb/virtual-account/transactions/{transaction_id}/retry.New accounts are active immediately after registration; no manual approval. Full docs: https://monapay.vn/llms-full.txt · OpenAPI: https://monapay.vn/openapi.json · Hotline 1900 636 648 · [email protected].

Prompt dán vào Claude Code / Codex / Cursor

Tích hợp MONA Pay (cổng thanh toán nội địa Việt Nam, nhận và xác nhận tiền chuyển khoản ngân hàng theo thời gian thực) vào dự án của tôi.

Tài liệu: https://monapay.vn/llms-full.txt (toàn văn), https://monapay.vn/openapi.json
Base URL API: https://api.monapay.vn (alias cũ: https://ipn.mona.host)
Xác thực: POST /api/v1/client/login {username,password} → data.access_token (Bearer, hạn 86400 giây).
POST/PUT/DELETE gửi thêm header X-Client-Secret (lấy từ POST /api/v1/client-keys/generate, hiện 1 lần).
Mọi response: {"success": bool, "message": str, "data": any}.

Việc cần làm:
1. Tạo endpoint HTTPS POST /webhook/monapay trong dự án để nhận thông báo tiền vào. MONA Pay POST JSON:
   {"amount":2500000,"description":"noi dung ck","transfer_date":"10:30:00 28/08/2026","transaction_code":"FT26240001234","account_number":"MONA0000010234","bank_name":"ACB","type":"income"}
2. Verify chữ ký: header X-Mona-Signature = "sha256=" + hex(HMAC-SHA256(secret, X-Mona-Timestamp + "." + raw_body)).
   Từ chối nếu |now - X-Mona-Timestamp| > 300 giây. So sánh chữ ký bằng hàm timing-safe. Đọc raw body, không parse trước khi ký.
3. Chống trùng bằng transaction_code (UNIQUE). Bỏ qua transaction_code = "DUMMY123" (payload gửi thử).
4. Trả HTTP 200 ngay trong 10 giây, xử lý đơn hàng bất đồng bộ. Khớp đơn theo account_number (số VA) hoặc mã đơn trong description, và so amount với số tiền đơn.
5. Đăng ký URL webhook: POST /api/v1/client-webhooks {name, webhook_url, auth_type:"HMAC_SHA256", secret_key} (Bearer + X-Client-Secret), rồi gửi thử POST /api/v1/client-webhooks/test {webhook_url, auth_type, secret_key, is_dummy:true}.
Secret HMAC và client_secret đọc từ biến môi trường MONA_WEBHOOK_SECRET, MONA_SECRET; không ghi cứng.

Chuỗi curl từ đầu đến cuối

Thay shopabc, mật khẩu và URL bằng của anh chị. Tài khoản đăng ký xong dùng ngay, không cần ai duyệt.

BASE=https://api.monapay.vn

# 1. Đăng nhập lấy Bearer token (hạn 24 giờ)
TOKEN=$(curl -s -X POST $BASE/api/v1/client/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"shopabc","password":"MatKhauManh#2026"}' | jq -r .data.access_token)

# 2. Sinh client_secret (chỉ hiện 1 lần, ghi vào .env ngay)
SECRET=$(curl -s -X POST $BASE/api/v1/client-keys/generate \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"agent-integration"}' | jq -r .data.client_secret)

# 3. Secret HMAC do anh chị tự sinh, dùng chung giữa MONA Pay (ký) và server anh chị (verify)
WEBHOOK_SECRET=$(openssl rand -hex 32)

# 4. Đăng ký URL webhook với HMAC-SHA256
curl -s -X POST $BASE/api/v1/client-webhooks \
  -H "Authorization: Bearer $TOKEN" -H "X-Client-Secret: $SECRET" -H 'Content-Type: application/json' \
  -d "{\"name\":\"Web ban hang\",\"webhook_url\":\"https://shop.vn/webhook/monapay\",\"auth_type\":\"HMAC_SHA256\",\"secret_key\":\"$WEBHOOK_SECRET\",\"payload_format\":\"application/json\"}"

# 5. Gửi thử payload giả lập (transaction_code = DUMMY123)
curl -s -X POST $BASE/api/v1/client-webhooks/test \
  -H "Authorization: Bearer $TOKEN" -H "X-Client-Secret: $SECRET" -H 'Content-Type: application/json' \
  -d "{\"webhook_url\":\"https://shop.vn/webhook/monapay\",\"auth_type\":\"HMAC_SHA256\",\"secret_key\":\"$WEBHOOK_SECRET\",\"is_dummy\":true}"

# 6. Đọc log lần gửi thử: status_code, duration_ms, error_label
curl -s "$BASE/api/v1/webhook-logs?limit=5" -H "Authorization: Bearer $TOKEN" | jq '.data.items[0]'

Giả lập MONA Pay bắn vào máy local (không cần tài khoản) để thử code verify:

SECRET='secret_hmac_test'
URL='http://localhost:3000/webhook/monapay'
TS=$(date +%s)
BODY='{"amount":2500000,"description":"noi dung ck","transfer_date":"10:30:00 28/08/2026","transaction_code":"FT26240001234","account_number":"MONA0000010234","bank_name":"ACB","type":"income"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -X POST "$URL" -H 'Content-Type: application/json' \
  -H "X-Mona-Timestamp: $TS" -H "X-Mona-Signature: sha256=$SIG" --data "$BODY"

Endpoint nhận webhook tối thiểu

PHP

<?php
$secret = getenv('MONA_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_MONA_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_MONA_SIGNATURE'] ?? '';
if (abs(time() - (int) $ts) > 300) { http_response_code(400); exit; }
if (!hash_equals('sha256=' . hash_hmac('sha256', "$ts.$raw", $secret), $sig)) { http_response_code(401); exit; }
http_response_code(200); echo 'OK';
if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
$d = json_decode($raw, true);
if ($d['transaction_code'] === 'DUMMY123') exit;
// INSERT IGNORE theo UNIQUE(transaction_code), rồi khớp đơn theo account_number / description, so amount

Node (Express)

app.post('/webhook/monapay', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.header('X-Mona-Timestamp') || '', sig = req.header('X-Mona-Signature') || '';
  const raw = req.body.toString('utf8');
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
  const exp = 'sha256=' + crypto.createHmac('sha256', process.env.MONA_WEBHOOK_SECRET).update(`${ts}.${raw}`).digest('hex');
  if (sig.length !== exp.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(exp))) return res.sendStatus(401);
  res.sendStatus(200);
  const d = JSON.parse(raw);
  if (d.transaction_code === 'DUMMY123') return;
  queue.add('monapay', d); // upsert theo transaction_code, khớp đơn, so amount
});

Bản đồ tài liệu cho agent

Cần gì Đọc
Toàn bộ trong 1 file /llms-full.txt
Mục lục ngắn /llms.txt
Đặc tả endpoint máy đọc /openapi.json
Xác thực, envelope /docs/api/xac-thuc.md
Payload webhook, chữ ký /docs/webhooks/dinh-dang-payload.md, /docs/webhooks/bao-mat.md
Tạo VA, QR /docs/api/tai-khoan-ao-va.md, /docs/api/qr-thanh-toan.md
Đối soát, gửi lại /docs/api/giao-dich.md, /docs/webhooks/gui-lai-va-xu-ly-loi.md
So với cổng quốc tế (PayPal, Stripe) /cong-thanh-toan-quoc-te
IP gửi webhook /docs/dia-chi-ip.md

Những điều agent hay hiểu sai

  • Không có refresh_token; hết 86.400 giây thì đăng nhập lại.
  • X-Client-Secret khác secret_key của webhook: cái đầu để gọi API MONA Pay, cái sau để MONA Pay ký payload gửi sang anh chị.
  • Ký trên raw body đúng byte MONA Pay gửi (JSON không khoảng trắng). Framework nào parse JSON trước rồi serialize lại sẽ sai chữ ký.
  • type hiện chỉ có income; đừng viết nhánh xử lý tiền ra như đã có.
  • Payload gửi thử có transaction_code = DUMMY123, amount = 500000, account_number = 1900636648; không tạo đơn từ payload này.
  • Tài khoản mới đăng nhập được ngay sau khi đăng ký, không cần MONA duyệt. Login trả 401 nghĩa là sai username/mật khẩu, không phải do chưa kích hoạt.