Quản lý tài khoản ngân hàng
Nội dung Markdown đầy đủ của trang tài liệu.
# Quản lý tài khoản ngân hàng
> **Môi trường:** phần hướng dẫn phía dưới áp dụng cho Production (`api-partner.pay2s.vn`). UAT (`api-partner-uat.pay2s.vn`) có phản hồi riêng; các mẫu đã gọi UAT ngày 11/10/2026 nằm ở mục “Request và response đối chiếu” cuối trang. API trực tiếp UAT hỗ trợ ACB/openapi và OTP thử `123456`; không dùng OTP này ở Production.
Các API dùng để liên kết, xác nhận OTP, xem danh sách, bật/tắt và xoá tài khoản ngân hàng.
**Base URL:** `https://api-partner.pay2s.vn`
Các API yêu cầu **Bearer Token** lấy từ `/v1/auth/authorize`. Xem [hướng dẫn xác thực](./authentication.md).
Thay dữ liệu minh hoạ bằng thông tin tài khoản cần liên kết. Giữ số tài khoản, số điện thoại, CCCD/MST và OTP ở kiểu chuỗi để bảo toàn số `0` ở đầu.
---
## 🔐 Header chung
```
Authorization: Bearer <token>
Content-Type: application/json
```
Gửi `Content-Type: application/json` với các request có body JSON.
---
## 1) Thêm ngân hàng
**POST** `https://api-partner.pay2s.vn/v1/banks`
Chọn mẫu theo phương thức kết nối: **OpenAPI** (`type: "openapi"`) hoặc **Internet Banking qua RPA** (`type: "personal"` cho cá nhân, `type: "business"` cho doanh nghiệp).
### Tham số trong request thêm tài khoản
| Trường | Kiểu | Cách sử dụng trong các mẫu |
|---|---|---|
| `type` | string | Phương thức kết nối: `openapi`, `personal` hoặc `business`. |
| `bankShortName` | string | Mã ngân hàng, ví dụ `BIDV`, `ACB`, `MBB`, `VCB`, `VTB`. |
| `accountNumber` | string | Số tài khoản cần liên kết. |
| `bank_type` | string | Mẫu ACB doanh nghiệp gửi `business`; mẫu cá nhân không gửi trường này. |
| `accName` | string | Tên chủ tài khoản trong mẫu OpenAPI. |
| `accMobile` | string | Số điện thoại đăng ký với ngân hàng trong mẫu OpenAPI. |
| `accEmail` | string | Email chủ tài khoản trong mẫu BIDV OpenAPI. |
| `cccd` | string | CCCD trong mẫu BIDV/MBBank cá nhân. |
| `merchantId` | string | Bắt buộc với BIDV OpenAPI; mã dùng để tạo tài khoản ảo (VA). |
| `username` | string | Tên đăng nhập Internet Banking với RPA, hoặc ACB OneBiz với ACB doanh nghiệp. |
| `password` | string | Mật khẩu Internet Banking với RPA. |
Bảng mô tả các trường của mẫu request, chưa phải danh sách validation đầy đủ. Quy định bắt buộc/tuỳ chọn và giới hạn độ dài theo từng ngân hàng cần được đối chiếu với backend.
### 1.1 OpenAPI – BIDV
```json
{
"type": "openapi",
"bankShortName": "BIDV",
"accountNumber": "0123456789",
"cccd": "012345678901",
"merchantId": "VYTEOO1",
"accName": "NGUYEN VAN A",
"accMobile": "0900000000",
"accEmail": "[email protected]"
}
```
- `merchantId`: yêu cầu với BIDV (được dùng khi sinh VA: tiền tố 963869 + mã).
- Hệ thống sẽ **gửi OTP** → cần gọi bước *Xác nhận OTP* bên dưới.
### 1.2 OpenAPI – ACB
#### Cá nhân
```json
{
"type": "openapi",
"bankShortName": "ACB",
"accountNumber": "19354957",
"accName": "NGUYEN VAN A",
"accMobile": "0900000000"
}
```
#### Doanh nghiệp
```json
{
"type": "openapi",
"bank_type": "business",
"bankShortName": "ACB",
"accountNumber": "99979986",
"accName": "CONG TY DEMO",
"accMobile": "0900000000",
"username": "acb_onebiz_user"
}
```
Với ACB doanh nghiệp, gửi `bank_type: "business"` và `username` của ACB OneBiz. Mẫu cá nhân ở trên không gửi `bank_type`; giá trị mặc định của trường này cần được xác nhận với backend.
### 1.3 OpenAPI – MBBank
#### Cá nhân
```json
{
"type": "openapi",
"bankShortName": "MBB",
"accountNumber": "737478888",
"accName": "NGUYEN VAN A",
"accMobile": "0900000000",
"cccd": "012345678901"
}
```
Với MBBank, mẫu trên dành cho cá nhân. Trường `cccd` dùng CCCD của chủ tài khoản; với hộ kinh doanh/doanh nghiệp, cần xác nhận yêu cầu MST và các trường bổ sung trước khi dùng mẫu này.
### 1.4 Pay2S-api – Cá nhân (Internet Banking qua RPA sử dụng cho các ngân hàng Vietcombank, Vietinbank)
```json
{
"type": "personal",
"bankShortName": "VCB",
"accountNumber": "0123456789",
"username": "internet_banking_user",
"password": "secret"
}
```
### 1.5 Pay2S-api – Doanh nghiệp (Internet Banking qua RPA sử dụng cho các ngân hàng Vietcombank, Vietinbank, Techcombank)
```json
{
"type": "business",
"bankShortName": "VTB",
"accountNumber": "0123456789",
"username": "internet_banking_user",
"password": "secret"
}
```
Với RPA, dùng tên đăng nhập và mật khẩu của đúng ngân hàng đã chọn.
### Xử lý sau khi gửi yêu cầu liên kết
1. Nếu phản hồi có `OTP = 1`, tiếp tục bước **2) Xác nhận OTP** cho tài khoản vừa gửi.
2. Sau khi xác nhận thành công, gọi `GET /v1/banks` để kiểm tra tài khoản và trạng thái liên kết.
3. Nếu phản hồi báo lỗi, xử lý theo nội dung lỗi trước khi tiếp tục.
Không dùng riêng thông báo “Hợp lệ” hoặc việc thiếu trường `OTP` để kết luận tài khoản đã được kích hoạt. Xem [phạm vi xác minh](#verification) về response và xử lý lỗi OTP.
---
## 2) Xác nhận OTP (Confirm OTP)
**POST** `https://api-partner.pay2s.vn/v1/banks/confirm-otp`
Gửi OTP của đúng yêu cầu liên kết. Giữ nguyên `type`, `bankShortName`, `accountNumber` so với bước thêm tài khoản; với BIDV giữ nguyên `merchantId`, với RPA cá nhân giữ nguyên `username` và `password`.
### 2.1 BIDV (OpenAPI)
```json
{
"type": "openapi",
"bankShortName": "BIDV",
"accountNumber": "0123456789",
"merchantId": "VYTEOO1",
"otp": "016311"
}
```
### 2.2 ACB hoặc MBBank (OpenAPI)
Ví dụ dưới đây dùng ACB. Với MBBank, thay `bankShortName` bằng `MBB` và dùng số tài khoản MBBank đã gửi ở bước thêm.
```json
{
"type": "openapi",
"bankShortName": "ACB",
"accountNumber": "19354957",
"otp": "767523"
}
```
### 2.3 Pay2S‑api (Personal)
```json
{
"type": "personal",
"bankShortName": "VCB",
"accountNumber": "0123456789",
"username": "internet_banking_user",
"password": "secret",
"otp": "767523"
}
```
Nếu vẫn nhận được yêu cầu OTP hoặc lỗi xác nhận, chưa đánh dấu tài khoản là đã liên kết. Kiểm tra mã và thông báo trả về; thời hạn OTP, số lần thử và cách gửi lại cần được xác nhận theo từng ngân hàng.
---
## 3) Danh sách tài khoản ngân hàng {#bank-list}
**GET** `https://api-partner.pay2s.vn/v1/banks`
Không gửi body. Lấy `id` của tài khoản trong danh sách để dùng cho các endpoint bật/tắt, xoá và xác nhận xoá; `{id}` không phải số tài khoản.
**Phản hồi mẫu:**
```json
{
"status": true,
"message": [
{
"id": 410,
"username": "0123456789",
"name": "NGUYEN VAN A",
"accountNumber": "0123456789",
"vaNumber": "963869789",
"balance": 1000000,
"created_at": "2025-10-14 10:42:11",
"status": 1,
"statusText": "Đang hoạt động",
"bankName": "Asia Commercial Bank"
}
]
}
```
Trong mẫu trên, `status` ở cấp ngoài là kết quả request; `message` là mảng tài khoản. `message[].status` là trạng thái từng tài khoản, với `1` tương ứng “Đang hoạt động” trong ví dụ. Các giá trị trạng thái khác cần được xác nhận với backend.
Với BIDV OpenAPI, `vaNumber` được mô tả theo dạng `963869<code>`. Khi sử dụng VA, lấy giá trị API trả về thay vì tự ghép từ mẫu.
---
## 4) Bật / Tắt trạng thái ngân hàng
**PATCH** `https://api-partner.pay2s.vn/v1/banks/{id}/status`
Request bật/tắt không gửi body theo Postman Collection (`Toggle Status`). Dùng `id` lấy từ danh sách tài khoản. Sau khi gọi, kiểm tra `newStatus` và đọc lại danh sách để xác nhận trạng thái; không tự động gọi lặp lại khi chưa biết kết quả lần trước.
**Phản hồi mẫu:**
```json
{
"status": true,
"message": "Cập nhật trạng thái ngân hàng thành công.",
"newStatus": 1
}
```
Chỉ bật được khi liên kết đã thành công. `newStatus` là trạng thái sau khi cập nhật; bảng giá trị đầy đủ và hành vi khi gọi lặp lại cần được xác nhận với backend.
---
## 5) Xoá ngân hàng
**DELETE** `https://api-partner.pay2s.vn/v1/banks/{id}`
Không gửi body. Nếu phản hồi có `OTP = 1`, yêu cầu xoá đang chờ xác nhận: tiếp tục bước **5.1** với cùng `{id}`. Không coi `status: true` trong phản hồi này là đã xoá xong.
**Phản hồi mẫu:**
```json
{
"status": true,
"message": "Xóa ngân hàng.",
"OTP": 1,
"type": "SMS"
}
```
### 5.1 Xác nhận xoá (OTP)
**POST** `https://api-partner.pay2s.vn/v1/banks/{id}/delete-confirm`
```json
{ "otp": "160241" }
```
**Phản hồi mẫu:**
```json
{ "status": true, "message": "Xóa ngân hàng thành công" }
```
---
## 6) Thống kê tóm tắt (Summary)
**GET** `https://api-partner.pay2s.vn/v1/banks/summary`
> Trả về tổng quan số lượng ngân hàng đã liên kết theo từng `bankName/shortBankName`, kèm tổng số.
**Ví dụ phản hồi rút gọn:**
```json
{
"status": true,
"bankCounts": [
{ "bankName": "Vietcombank", "shortBankName": "VCB", "count": 2 },
{ "bankName": "BIDV", "shortBankName": "BIDV", "count": 1 }
],
"total": 3
}
```
---
## 🧪 cURL mẫu
Các lệnh dưới đây dùng cú pháp Bash. Thay `<token>` và dữ liệu mẫu trước khi chạy.
### Thêm BIDV (OpenAPI)
```bash
curl -X POST https://api-partner.pay2s.vn/v1/banks \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "openapi",
"bankShortName": "BIDV",
"accountNumber": "0123456789",
"cccd": "012345678901",
"merchantId": "VYTEOO1",
"accName": "NGUYEN VAN A",
"accMobile": "0900000000",
"accEmail": "[email protected]"
}'
```
### Xác nhận OTP BIDV
```bash
curl -X POST https://api-partner.pay2s.vn/v1/banks/confirm-otp \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "openapi",
"bankShortName": "BIDV",
"accountNumber": "0123456789",
"merchantId": "VYTEOO1",
"otp": "016311"
}'
```
### Danh sách ngân hàng
```bash
curl -X GET "https://api-partner.pay2s.vn/v1/banks" \
-H "Authorization: Bearer <token>"
```
### Bật/Tắt trạng thái
```bash
curl -X PATCH "https://api-partner.pay2s.vn/v1/banks/410/status" \
-H "Authorization: Bearer <token>"
```
### Xoá ngân hàng
```bash
curl -X DELETE "https://api-partner.pay2s.vn/v1/banks/410" \
-H "Authorization: Bearer <token>"
```
### Xác nhận xoá (OTP)
```bash
curl -X POST "https://api-partner.pay2s.vn/v1/banks/410/delete-confirm" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "otp": "160241" }'
```
---
## Phạm vi xác minh và thông tin cần bổ sung {#verification}
Các request đã được đối chiếu với [Postman Collection công khai](https://docs.pay2s.vn/downloads/pay2s-partner.postman_collection.json). Collection không lưu response mẫu và có request “List Bank” khai báo `POST` kèm body thêm tài khoản; vì vậy chưa thể dùng collection để xác nhận schema response hoặc phương thức lấy danh sách. Các response trên trang là ví dụ từ tài liệu hiện có.
Những chi tiết cần xác nhận với backend trước khi hoàn thiện tích hợp:
- Schema response thành công, chờ OTP và lỗi của bước thêm/xác nhận; HTTP status và mã lỗi tương ứng.
- Thời hạn OTP, giới hạn số lần thử, cách gửi lại và quy trình OTP cho RPA doanh nghiệp.
- Quy tắc bắt buộc/tuỳ chọn, giá trị mặc định của `bank_type` và validation theo từng ngân hàng; mẫu ACB doanh nghiệp và MBBank chưa có request riêng trong collection.
- Schema thực tế của danh sách tài khoản, bảng trạng thái và hành vi bật/tắt khi request được gửi lại.
Khi gặp `401 Unauthorized`, kiểm tra Bearer Token và lấy token mới theo [hướng dẫn xác thực](./authentication.md) nếu token đã hết hạn. Với lỗi OTP, dùng thông báo thực tế để hướng dẫn người dùng; tài liệu chưa xác định endpoint gửi lại OTP.
## Request và response đối chiếu ngày 11/10/2026
Các mẫu dưới đây giữ cấu trúc trường và kiểu dữ liệu. Khóa, chữ ký, URL phiên đã được thay bằng placeholder; danh sách chỉ giữ tối đa hai phần tử và dữ liệu ảnh/PDF/XML dài được rút gọn. Không sao chép placeholder để gọi API thật.
### Danh sách ngân hàng đã liên kết
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-list">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-list">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`GET /v1/banks`
Tham số path/query (không gửi JSON body):
```json
{}
```
HTTP 200. Response:
```json
{
"status": true,
"message": [
{
"id": 19,
"name": "NGUYEN VAN DEMO",
"accountNumber": "P2SUAT7A52E214CB4A2F227852",
"vaNumber": "",
"status": 1,
"shortBankName": "ACB",
"bankName": "Ngân hàng Á Châu (ACB)",
"customer_reference": "DEV_5425ec25f88b87b7a38fa5a2a1111495_179165667730313cc72",
"simulated": true,
"demo_user_bank_id": 5179
},
{
"id": 18,
"name": "NGUYEN DEV PAY2S",
"accountNumber": "P2SUATABF651203269273FDD39",
"vaNumber": "",
"status": 1,
"shortBankName": "ACB",
"bankName": "Ngân hàng Á Châu (ACB)",
"customer_reference": "DEV_97ba780a4affdc6c936c98bbeabd2a52_1791656524283169d3f",
"simulated": true,
"demo_user_bank_id": 5178
}
]
}
```
### Thống kê ngân hàng
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-summary">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-summary">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`GET /v1/banks/summary`
Tham số path/query (không gửi JSON body):
```json
{}
```
HTTP 200. Response:
```json
{
"success": true,
"data": {
"total": 6,
"active": 6,
"inactive": 0,
"simulated": true
}
}
```
### Thêm ngân hàng bằng API
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-add">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-add">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`POST /v1/banks`
Body:
```json
{
"type": "openapi",
"bankShortName": "ACB",
"accountNumber": "1791659973755786",
"accName": "TAI KHOAN DEMO",
"accMobile": "0900000000",
"customer_reference": "DEV_d33eb1382b55f87313b6840bff80f880_179165997375564ac8e"
}
```
HTTP 200. Response:
```json
{
"status": true,
"message": "Gửi OTP giả lập thành công.",
"OTP": 1,
"type": "openapi",
"data": {
"session_id": "4a4a0832eb84405d5a643be0a961803c",
"status": "awaiting_otp",
"environment": "uat",
"bank": "ACB",
"purpose": "LINK_BANK_ACCOUNT",
"unlink_mode": "bank",
"parent_origin": "",
"partner_name": "Demo UAT",
"bank_choices": [
{
"id": "acb_personal",
"bank_code": "ACB",
"name": "Ngân hàng Á Châu",
"account_type": "personal",
"account_type_label": "Cá nhân",
"connection_type": "openapi",
"fields": [
"account_name",
"phone"
]
},
{
"id": "acb_business",
"bank_code": "ACB",
"name": "Ngân hàng Á Châu",
"account_type": "business",
"account_type_label": "Doanh nghiệp",
"connection_type": "openapi",
"fields": [
"account_name",
"phone"
]
}
],
"profile_id": "acb_personal",
"branding": {
"partnerName": "Demo UAT",
"primaryColor": "#09834d",
"partnerLogo": "",
"supportUrl": "",
"privacyUrl": ""
},
"expires_at": "2026-10-10 19:34:34Z",
"error": null,
"account_masked": "••••5786",
"return_url": ""
}
}
```
### Xác nhận OTP ngân hàng
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-otp">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-otp">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`POST /v1/banks/confirm-otp`
Body:
```json
{
"type": "openapi",
"bankShortName": "ACB",
"accountNumber": "1791659973755786",
"otp": "123456"
}
```
HTTP 200. Response:
```json
{
"status": true,
"message": "Liên kết giả lập thành công.",
"data": {
"session_id": "4a4a0832eb84405d5a643be0a961803c",
"status": "connected",
"environment": "uat",
"bank": "ACB",
"purpose": "LINK_BANK_ACCOUNT",
"unlink_mode": "bank",
"parent_origin": "",
"partner_name": "Demo UAT",
"bank_choices": [
{
"id": "acb_personal",
"bank_code": "ACB",
"name": "Ngân hàng Á Châu",
"account_type": "personal",
"account_type_label": "Cá nhân",
"connection_type": "openapi",
"fields": [
"account_name",
"phone"
]
},
{
"id": "acb_business",
"bank_code": "ACB",
"name": "Ngân hàng Á Châu",
"account_type": "business",
"account_type_label": "Doanh nghiệp",
"connection_type": "openapi",
"fields": [
"account_name",
"phone"
]
}
],
"profile_id": "acb_personal",
"branding": {
"partnerName": "Demo UAT",
"primaryColor": "#09834d",
"partnerLogo": "",
"supportUrl": "",
"privacyUrl": ""
},
"expires_at": "2026-10-10 19:34:34Z",
"error": null,
"account_masked": "••••5786",
"return_url": ""
}
}
```
### Bật / tắt ngân hàng
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-toggle">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-toggle">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`PATCH /v1/banks/{id}/status`
Body:
```json
{}
```
HTTP 200. Response:
```json
{
"status": true,
"message": "Đã đổi trạng thái giả lập.",
"data": {
"id": 21,
"status": 0
}
}
```
### Yêu cầu xóa ngân hàng
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-delete">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-delete">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`DELETE /v1/banks/{id}`
Tham số path/query (không gửi JSON body):
```json
{}
```
HTTP 200. Response:
```json
{
"status": true,
"OTP": 1,
"message": "Nhập OTP giả lập 123456 để hủy liên kết."
}
```
### OTP xác nhận xóa ngân hàng
<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=bank-delete-confirm">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience href="/demos/?flow=bankhub&api=bank-delete-confirm">Trải nghiệm BankHub Hosted Link ↗</a></div>
**Đã gọi API demo/UAT.**
`POST /v1/banks/{id}/delete-confirm`
Body:
```json
{
"otp": "123456"
}
```
HTTP 200. Response:
```json
{
"status": true,
"message": "Đã hủy liên kết giả lập."
}
```