{"_rev":"7-2448c10e233c0f4ea30235b953666f0d","time":{"created":"2026-08-20T14:30:18.071Z","modified":"2026-08-20T14:30:18.577Z","0.1.0":"2026-08-20T13:59:44.605Z","0.0.1":"2026-08-20T14:10:29.717Z","0.0.2":"2026-08-20T14:30:18.365Z"},"_id":"@banklookup/sdk","name":"@banklookup/sdk","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.2":{"name":"@banklookup/sdk","version":"0.0.2","description":"SDK chính thức cho BankLookup API — API hỗ trợ kết nối dịch vụ ngân hàng","keywords":["banklookup","bank","vietqr","napas","vietnam","bank-account","lookup","sdk"],"license":"MIT","author":{"name":"BankLookup"},"homepage":"https://banklookup.net/document","type":"module","sideEffects":false,"engines":{"node":">=18"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","gen:types":"openapi-typescript ../lookup-api/docs/openapi.yaml -o src/generated/api.ts","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"devDependencies":{"@types/node":"^24.0.0","openapi-typescript":"^7.9.0","tsup":"^8.5.0","typescript":"^5.9.0","vitest":"^3.2.0"},"publishConfig":{"access":"public","provenance":true},"_id":"@banklookup/sdk@0.0.2","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-KJnziCDLVrV83BafMmAwSbVzPm3Z7esQW2I6iYexw8FnbOBILGQR72hSonDtqbrRbD+rwQ/pBM5y/MOqsss3ew==","shasum":"540b5789d755ac2c710952a571bff503446bca91","tarball":"https://registry.npmjs.org/@banklookup/sdk/-/sdk-0.0.2.tgz","fileCount":10,"unpackedSize":182653,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD3QF1GSMli0CJlTuEzeK53GRqvMgyz5avmOzEQLC7GIAIgNqjn19e3rbtHX6Nkuzv2bbV723/DOwwcVor+5Y57UrM="}]},"_npmUser":{"name":"banklookup","email":"contact@banklookup.net"},"directories":{},"maintainers":[{"name":"banklookup","email":"contact@banklookup.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.0.2_1787236218223_0.9757887294112964"},"_hasShrinkwrap":false}},"maintainers":[{"name":"banklookup","email":"contact@banklookup.net"}],"description":"SDK chính thức cho BankLookup API — API hỗ trợ kết nối dịch vụ ngân hàng","homepage":"https://banklookup.net/document","keywords":["banklookup","bank","vietqr","napas","vietnam","bank-account","lookup","sdk"],"author":{"name":"BankLookup"},"license":"MIT","readme":"# @banklookup/sdk\n\nSDK chính thức của [BankLookup API](https://banklookup.net/document), dùng để tra cứu tên chủ tài khoản ngân hàng Việt Nam, đọc mã VietQR và xác thực tên chủ thể.\n\n- Không phải cài thêm thư viện nào, SDK chỉ dùng `fetch` có sẵn\n- Hỗ trợ TypeScript đầy đủ, kiểu dữ liệu lấy thẳng từ spec của server\n- Mỗi loại lỗi là một lớp riêng, bạn bắt bằng `instanceof` thay vì phải so chuỗi\n- Tự xếp hàng cho đỡ dính rate limit, và chỉ thử lại khi chắc chắn không bị tính tiền hai lần\n\n## Cài đặt\n\n```bash\nnpm install @banklookup/sdk\n```\n\nSDK cần Node.js 18 trở lên. Ngoài ra bạn dùng được với Bun, Deno, Cloudflare Workers, hay bất kỳ runtime nào có sẵn `fetch`.\n\n## Bắt đầu\n\n```ts\nimport { BankLookup } from '@banklookup/sdk'\n\nconst client = new BankLookup({\n  apiKey: process.env.BANKLOOKUP_API_KEY,\n  apiSecret: process.env.BANKLOOKUP_API_SECRET,\n})\n\nconst account = await client.lookup({ bank: 'VCB', account: '1012345678' })\nconsole.log(account.ownerName) // NGUYEN VAN A\n```\n\nVới `bank`, bạn truyền mã ngân hàng hay BIN 6 số đều được.\n\n> **Chỉ nên gọi từ phía server.** Chỉ cần `x-api-secret` lọt vào bundle trình duyệt là bạn mất key, nên SDK sẽ từ chối khởi tạo khi thấy mình đang chạy trong trình duyệt. Nếu vẫn muốn chạy, bạn phải bật `dangerouslyAllowBrowser: true`.\n\n## Các thao tác\n\n| Method | Endpoint | Tốn credit |\n|---|---|:---:|\n| `lookup({ bank, account })` | `POST /` | 1 |\n| `lookupQrContent({ content })` | `POST /qr-content` | 1 |\n| `lookupQrImage({ file })` | `POST /qr-image` | 1 |\n| `ownerCheck({ bank, account, ownerName })` | `POST /owner-check` | 1 |\n| `credit()` | `POST /my-credit` | — |\n| `banks()` | `GET /bank/list` | — |\n\nAPI bọc mọi kết quả trong một lớp envelope `{ code, success, data, msg, timestamp }`. SDK bóc sẵn lớp đó rồi, nên bạn nhận được luôn phần `data` và dùng ngay. Kiểu của từng response cũng được export sẵn:\n\n```ts\nimport type { BankInfo, Credit, LookupResult, OwnerCheckResult, QrLookupResult } from '@banklookup/sdk'\n```\n\n### Tra cứu theo số tài khoản\n\nTrường `bank` nhận cả mã lẫn BIN, viết hoa hay viết thường đều được, server sẽ tự nhận ra bạn đang gửi loại nào:\n\n```ts\n// theo mã ngân hàng\nawait client.lookup({ bank: 'VCB', account: '1012345678' })\n\n// hoặc theo BIN 6 số, kết quả như nhau\nconst result = await client.lookup({ bank: '970436', account: '1012345678' })\n\n// LookupResult\n// {\n//   ownerName: 'NGUYEN VAN A',  // tên ngân hàng trả về, luôn viết hoa không dấu\n//   bank: 'VCB',                // mã đã chuẩn hoá, kể cả khi bạn gửi lên BIN\n//   account: '1012345678',\n// }\n```\n\nKiểu của `ownerName` là `string | null` cho khớp với spec, nhưng hễ request thành công thì trường này luôn có giá trị, vì không tra ra chủ tài khoản là server đã trả lỗi `NOT_FOUND` rồi. Còn nếu cần bảng ánh xạ giữa mã và BIN, bạn gọi `banks()`.\n\n### Tra cứu bằng mã VietQR\n\n```ts\n// từ chuỗi TLV đã đọc sẵn khỏi mã QR\nconst byContent = await client.lookupQrContent({ content: qrString })\n\n// hoặc từ file ảnh (png/jpg/jpeg, tối đa 2 MB)\nimport { readFile } from 'node:fs/promises'\n\nconst byImage = await client.lookupQrImage({\n  file: await readFile('./qr.png'),\n  filename: 'qr.png',\n})\n\n// QrLookupResult, giống lookup() nhưng có thêm hai trường đọc từ mã QR\n// {\n//   ownerName: 'NGUYEN VAN A',\n//   bank: 'VCB',\n//   account: '1012345678',\n//   amount: '50000',                // null nếu mã QR không mang số tiền\n//   content: 'thanh toan don hang', // null nếu mã QR không có nội dung chuyển khoản\n// }\n```\n\nTham số `file` nhận `Blob`/`File`, `ArrayBuffer` hoặc `Uint8Array`. Ảnh nặng hơn 2 MB sẽ bị chặn ngay ở client nên bạn không tốn request nào.\n\nServer nhận dạng ảnh dựa vào phần mở rộng của tên file. Vậy nên với ảnh JPEG, bạn nhớ truyền thêm `filename: 'qr.jpg'` hoặc `contentType: 'image/jpeg'` để SDK đặt tên cho khớp.\n\n### Xác thực tên chủ thể\n\n```ts\nimport { NotFoundError } from '@banklookup/sdk'\n\ntry {\n  const checked = await client.ownerCheck({\n    bank: 'VCB',\n    account: '1012345678',\n    ownerName: 'Nguyen Van A',\n  })\n\n  // OwnerCheckResult, chỉ có khi tên khớp\n  // {\n  //   status: true,\n  //   message: 'SUCCESS',\n  //   ownerName: 'Nguyen Van A',  // giữ nguyên chuỗi bạn gửi lên, cả dấu lẫn hoa thường\n  //   bank: 'VCB',\n  //   account: '1012345678',\n  // }\n} catch (error) {\n  if (error instanceof NotFoundError) {\n    // BANK_ACCOUNT_NAME_NOT_MATCH\n  }\n}\n```\n\nTên được so khớp sau khi bỏ dấu tiếng Việt, và cũng không phân biệt hoa thường. Có hai chỗ khác với `lookup()`: endpoint này **chỉ nhận mã ngân hàng chứ không nhận BIN**, và số ngân hàng hỗ trợ cũng ít hơn.\n\n### Credit và danh mục ngân hàng\n\n```ts\nconst { value } = await client.credit()\n// Credit\n// { value: 12500 }\n\nconst banks = await client.banks() // không cần API key\nconst supported = banks.filter((bank) => bank.lookup_supported)\n\n// BankInfo[], mỗi phần tử có dạng\n// {\n//   id: '0f1d2c3b-4a59-4c8d-9e0f-1a2b3c4d5e6f',\n//   name: 'Ngân hàng TMCP Ngoại thương Việt Nam',\n//   bin: '970436',\n//   code: 'VCB',\n//   short_name: 'Vietcombank',\n//   logo_url: 'https://api.vietqr.io/img/VCB.png',\n//   icon_url: 'https://api.vietqr.io/img/VCB.png',\n//   lookup_supported: true,  // false thì lookup() sẽ trả lỗi BANK_NOT_SUPPORTED\n//   swift_code: 'BFTVVNVX',\n// }\n```\n\n## Xử lý lỗi\n\nMọi lỗi đều kế thừa `BankLookupError` và mang theo `status` là mã HTTP, `code` là mã lỗi nghiệp vụ mà server trả về.\n\n```ts\nimport { BankLookupError, OutOfCreditError, RateLimitError } from '@banklookup/sdk'\n\ntry {\n  await client.lookup({ bank: 'VCB', account: '1012345678' })\n} catch (error) {\n  if (error instanceof OutOfCreditError) {\n    // hết credit, nạp thêm rồi chạy lại\n  } else if (error instanceof RateLimitError) {\n    console.log(`chờ ${error.retryAfter} giây`)\n  } else if (error instanceof BankLookupError) {\n    console.error(error.status, error.code)\n  }\n}\n```\n\n| Lớp lỗi | HTTP | Mã thường gặp |\n|---|---|---|\n| `AuthError` | 422 / 403 | `MISSING_HEADER`, `API_INFO_NOT_FOUND`, `API_NOT_ACTIVATED` |\n| `ForbiddenError` | 403 | `IP_NOT_ALLOWED`, `ACCOUNT_BLOCKED`, `ACCOUNT_BLACKLIST` |\n| `OutOfCreditError` | 402 | `OUT_OF_CREDIT` |\n| `NotFoundError` | 422 | `NOT_FOUND`, `BANK_ACCOUNT_NAME_NOT_MATCH`, `QR_NOT_FOUND` |\n| `ValidationError` | 422 | dữ liệu gửi lên sai định dạng, chi tiết nằm ở `error.issues` |\n| `RateLimitError` | 429 | `TOO_MANY_REQUESTS`, kèm `retryAfter`, `limit`, `remaining`, `resetAt` |\n| `ServerError` | 5xx | lỗi phía server |\n| `TimeoutError` | | request vượt quá `timeout` |\n| `ConnectionError` | | không kết nối được tới server |\n| `InvalidInputError` | | tham số sai, SDK phát hiện ngay tại client |\n\nVới lỗi từ server thì `error.message` chính là `error.code` cho bạn dễ grep log. Riêng nhóm lỗi phát sinh ở client, `message` sẽ là câu mô tả đầy đủ.\n\n## Credit, thử lại và rate limit\n\nMỗi lượt tra cứu tốn 1 credit, kể cả khi không tìm ra chủ tài khoản (lỗi 422 `NOT_FOUND`). Ngược lại, nếu hệ thống gặp sự cố và trả lỗi 5xx thì lượt đó không được ghi nhận, nên bạn không bị trừ credit.\n\nSDK dựa vào đúng điều này để quyết định lúc nào nên thử lại, nhiều nhất là `maxRetries` lần (mặc định 2):\n\n| Tình huống | Thử lại | Lý do |\n|---|:---:|---|\n| Dính rate limit (429) | có | Request bị chặn từ trước khi chạm tới credit. SDK chờ đúng số giây server báo trong `Retry-After`, còn nếu phải chờ quá 60 giây thì báo lỗi luôn cho bạn xử lý |\n| Server lỗi (5xx) | có | Lượt tra cứu hỏng giữa chừng thì không bị tính tiền |\n| Timeout hoặc lỗi mạng khi tra cứu | không | Lúc này không biết server đã xử lý tới đâu, credit có thể đã bị trừ rồi. Thử lại hay không là quyết định của bạn |\n| Timeout hoặc lỗi mạng ở `credit()`, `banks()` | có | Hai endpoint này vốn không tốn credit |\n\nServer có áp rate limit riêng cho từng API key, gọi quá dày thì bạn sẽ nhận lỗi 429. SDK mặc định tự xếp hàng theo đúng hạn mức hiện hành nên bình thường bạn không cần bận tâm, nhưng vẫn chỉnh được:\n\n```ts\n// chạy song song nhiều tiến trình thì hạ hạn mức của từng tiến trình xuống\nconst client = new BankLookup({\n  apiKey,\n  apiSecret,\n  throttle: { lookupPerSecond: 5 },\n})\n\n// hoặc tắt hẳn để tự quản lý nhịp gọi\nconst raw = new BankLookup({ apiKey, apiSecret, throttle: false })\n```\n\nBộ đếm của throttle nằm riêng trong từng tiến trình chứ không chia sẻ với nhau. Vậy nên khi chạy nhiều worker cùng lúc, bạn nhớ chia nhỏ hạn mức tương ứng.\n\n## Tuỳ chọn khởi tạo\n\n| Tuỳ chọn | Mặc định | Mô tả |\n|---|---|---|\n| `apiKey`, `apiSecret` | | Lấy tại trang chủ [banklookup.net](https://banklookup.net). Bỏ trống thì bạn chỉ gọi được `banks()` |\n| `baseUrl` | `https://api.banklookup.net` | Đổi khi cần trỏ về server chạy local |\n| `timeout` | `30000` | Tính bằng ms. Một lượt tra cứu có thể phải thử qua nhiều nguồn nên đừng đặt quá ngắn |\n| `maxRetries` | `2` | Số lần thử lại nhiều nhất, xem bảng ở mục trên |\n| `throttle` | `true` | Đặt `false` để tắt, hoặc truyền `{ lookupPerSecond, infoPerSecond }` |\n| `fetch` | `globalThis.fetch` | Dùng `fetch` riêng khi cần đi qua proxy hoặc mock trong test |\n| `paths` | `PATHS` | Ghi đè đường dẫn. Truyền `LEGACY_PATHS` nếu muốn trỏ về bề mặt `/api/bank/*` |\n| `userAgent` | | Chuỗi gắn thêm vào `User-Agent`, giúp bạn nhận ra ứng dụng của mình trong log server |\n| `onRetry` | | Callback chạy mỗi lần SDK thử lại, tiện để ghi log hoặc đẩy metric |\n| `dangerouslyAllowBrowser` | `false` | Cho phép chạy trong trình duyệt |\n\nTừng lời gọi cũng nhận tuỳ chọn riêng:\n\n```ts\nawait client.lookup({ bank: 'VCB', account: '1012345678' }, { timeout: 45_000, signal })\n```\n\n## Cloudflare Workers\n\n```ts\nimport { BankLookup } from '@banklookup/sdk'\n\nexport default {\n  async fetch(request, env) {\n    const client = new BankLookup({ apiKey: env.BANKLOOKUP_KEY, apiSecret: env.BANKLOOKUP_SECRET })\n    const result = await client.lookup({ bank: 'VCB', account: '1012345678' })\n    return Response.json(result)\n  },\n}\n```\n\n## Phát triển\n\n```bash\nnpm install\nnpm run gen:types   # sinh src/generated/api.ts từ lookup-api/docs/openapi.yaml\nnpm run typecheck\nnpm test\nnpm run build\n```\n\n## Giấy phép\n\nMã nguồn SDK phát hành theo giấy phép MIT. Việc sử dụng BankLookup API thì tuân theo [điều khoản dịch vụ](https://banklookup.net/dieu-khoan-su-dung).\n","readmeFilename":"README.md"}