# Giới thiệu v3.0

* NhanhAPI version **3.0**.

## Khởi tạo ứng dụng

* Xem cách khởi tạo ứng dụng [tại đây](https://apidocs.nhanh.vn/app).
* Xem cách đăng nhập cấp quyền và lấy accessToken [tại đây](https://apidocs.nhanh.vn/app#lay-access-token).

## Request params

* Sau khi lấy được accessToken, bạn có thể bắt đầu gọi các API. Mỗi service của Nhanh sẽ có các tên miền riêng:
  * [POS](https://nhanh.vn): **`https://pos.open.nhanh.vn`**
  * [Vpage](https://vpage.nhanh.vn): **`https://vpage.open.nhanh.vn`**
* Version 3.0 sẽ dùng **POST** method, headers, body **raw** và các params bắt buộc như sau:

  <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Chú ý:</strong> Nếu param type có dấu <span class="math">^{{\color{red}*}}</span> là bắt buộc (required), nếu không có là không bắt buộc (optional).</p></div>

| Param         | Type                          | Description                                                                                       |
| ------------- | ----------------------------- | ------------------------------------------------------------------------------------------------- |
| appId         | int $$^{{\color{red}\*}}$$    | ID app của bạn (Truyền lên link API)                                                              |
| businessId    | int $$^{{\color{red}\*}}$$    | ID doanh nghiệp trên Nhanh.vn. Lúc bạn lấy accessToken có trả về businessId (Truyền lên link API) |
| Authorization | string $$^{{\color{red}\*}}$$ | Access token của bạn (Truyền lên Headers)                                                         |

### Paginator

* **Request**: Khi gọi API, bạn có thể truyền thêm paginator để lấy các dữ liệu phân trang. Paginator thường có 3 field chính:
  * `size` (int): Số lượng bản ghi trên 1 trang. Mặc định tối đa không quá 100, nếu API hỗ trợ số lớn hơn, sẽ được mô tả rõ ở từng API.
  * `sort` (array | object): Các tiêu chí sắp xếp, xem mô tả ở tài liệu của từng API.
  * `next` (array | object): Dùng để gọi dữ liệu cho trang tiếp theo.
    * Khi gọi API cho trang đầu tiên, thì không cần truyền `next`, khi nhận được response có paginator.next, thì request tiếp theo bạn cần truyền `next` để lấy dữ liệu cho trang sau. Tùy từng API, hoặc cùng 1 API nhưng 1 số page cũng có thể có cấu trúc next trả về khác nhau.
  * **Lỗi thường hay gặp phải**:
    * Response trả về `"paginator":{"next":{"id":"xxx"}}`, thì request lấy trang tiếp theo cần truyền đúng `"paginator":{"next":{"id":"xxx"}}` chứ không phải `"paginator":{"next":"xxx"}`
    * Response trả về `"paginator":{"next":{"totalRevenue":"xxx", "productId":"yyy"}}`, thì request lấy trang tiếp theo cần truyền đúng `"paginator":{"next":{"totalRevenue":"xxx", "productId":"yyy"}}` chứ không phải `"paginator":{"next": {"xxx", "yyy"}}`.
    * Nếu response không trả về next, hoặc next bị NULL, nghĩa là đã hết dữ liệu, bạn không cần gọi API lấy trang tiếp theo.
    * Nếu response trả về dữ liệu ít hơn số lượng `size` (VD có 1 số tình huống dữ liệu bị xóa hoặc đã bị filter ẩn đi), nhưng vẫn có `next`, nghĩa là vẫn còn dữ liệu ở trang tiếp theo.
* **Response**:
  * `next` (array | object): Giá trị phân trang cho trang tiếp theo.

### Postman sample

* Lấy danh sách sản phẩm

```curl
curl --location --globoff 'https://pos.open.nhanh.vn/v3.0/product/list?appId={{appId}}&businessId={{businessId}}' \
--header 'Authorization: {{accessToken}}' \
--header 'Content-Type: application/json' \
--data '{
    "filters": {
        "name": "Áo sơ mi"
    },
    "paginator": {
        "size": 50,
        "sort": {"id": "desc"},
        "next": ""
    }
}'
```

* Thêm sản phẩm

```curl
curl --location --globoff 'https://pos.open.nhanh.vn/v3.0/product/add?appId={{appId}}&businessId={{businessId}}' \
--header 'Authorization: {{accessToken}}' \
--header 'Content-Type: application/json' \
--data '[
  {
    "appProductId": "string",
    "code": "Product Code 1",
    "barcode": "2000214262896",
    "name": "Product name 1",
    "price": 120000
  },
  {
    "appProductId": "string",
    "code": "Product Code 2",
    "barcode": "2000214262889",
    "name": "Product name 2",
    "price": 175000
  }
]'
```

### Postman collection

* Bạn có thể dùng [Postman Collection v3.0](https://www.postman.com/nhanh-vn/pos-open-nhanh-vn) tham khảo params cho 1 số API hay dùng.
* Để sử dụng: Bạn click vào collection mình cần -> click **Fork**
  * **Khuyến cáo**: Bạn nên tải Postman về, cài đặt và dùng ở localhost, Bản Postman chạy online trên web, có 1 số tình huống ghi nhận Response time bị sai.

![Postman: Tải về và tạo collection trên My Workspace của bạn](https://2299560279-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkqD6qBCDVbHh0yciWW1F%2Fuploads%2Fgit-blob-506d89fc1d35903484ebc68c43b39d69cea2fa25%2Fpostman_create_fork.png?alt=media)

* Ở phần **Create Fork**: điền **Fork label** (Tên collection ở Workspace của bạn), **Workspace** (Chọn một không gian làm việc mà bạn muốn phân nhánh), xong click **Fork Collection**

![Postman: Tải về và tạo collection trên My Workspace của bạn](https://2299560279-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkqD6qBCDVbHh0yciWW1F%2Fuploads%2Fgit-blob-a7e44525545cd353c6e27352ec5f5ca07b6c1296%2Fpostman_fork_collection.png?alt=media)

### Code sample

{% hint style="info" %}
Chú ý: Bạn có thể dùng Postman điền các params và click vào Code Snippet trên Postman để xem cách tạo syntax cho các ngôn ngữ (Nodejs, PHP, C#, Go, Java...):
{% endhint %}

![Postman generate code syntax](https://2299560279-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkqD6qBCDVbHh0yciWW1F%2Fuploads%2Fgit-blob-69ae97e3aaf9109a649337ac61813d2f2f6f3a3e%2Fpostman_generate_code_snippet.jpg?alt=media)

## Response

* NhanhAPI sẽ phản hồi cho 2 tình huống Successful `"code": 1` và Failed `"code": 0` như bên dưới.

### Successful response

* Tùy API sẽ có thể chỉ trả về `{"code": 1}`, có thể thêm `data` (object, array), `warning` (string, object, array).

```json
{
    "code": 1,
    "data": [
        {
            "appProductId": "X1aoCM",
            "id": 421509,
            "barcode": "2000214262919"
        }
    ]
}
```

```json
{
  "code": 1,
  "warning": ["orderPrivateId is deprected, you should use appOrderId"]
}
```

### Failed response

* Khi gọi API bị lỗi, bạn sẽ nhận được phản hồi `"code": 0`, và có thể kèm theo các field khác như:
  * `errorCode`: mã lỗi. Xem chi tiết [mã lỗi chung](#common-error-codes), mã lỗi riêng của [POS](#pos-common-error-codes) hoặc [Vpage](#vpage-common-error-codes).
  * `messages`: có thể là 1 **string** (VD: `"messages": "content"`) hoặc **array** (VD: `"messages" : ["field_1": "message_1", "field_2": "message_2"]`.
  * `data`: Dữ liệu lỗi kèm theo.

```json
{
  "code": 0,
  "errorCode": "ERR_INVALID_FORM_FIELDS",
  "messages": {
    "customer.mobile": "customer.mobile is required",
    "customer.email": "invalid customer.email"
  }
}
```

### Common error codes

* Bảng bên dưới mô tả mã lỗi chung cho cả POS và Vpage.

| errorCode                       | Description                                                                                                                                                                                                                                                                                                                                    |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ERR\_INVALID\_APP\_ID           | Invalid appId                                                                                                                                                                                                                                                                                                                                  |
| ERR\_INVALID\_BUSINESS\_ID      | Invalid businessId                                                                                                                                                                                                                                                                                                                             |
| ERR\_INVALID\_ACCESS\_TOKEN     | Invalid access token                                                                                                                                                                                                                                                                                                                           |
| ERR\_INVALID\_VERSION           | Invalid version. VD bạn lấy accessToken v2.0 gọi API v3.0                                                                                                                                                                                                                                                                                      |
| ERR\_EXCEEDED\_RATE\_LIMIT      | App của bạn đã vượt quá [API Rate Limit](#api-rate-limit)                                                                                                                                                                                                                                                                                      |
| ERR\_BUSINESS\_NOT\_ENABLE\_API | Doanh nghiệp chưa mở cài đặt cho phép dùng API.                                                                                                                                                                                                                                                                                                |
| ERR\_INVALID\_FORM\_FIELDS      | Trường dữ liệu không hợp lệ, khi gặp mã lỗi này thì thường `messages` sẽ trả về mảng `["field": "error" ]`, VD `["id": "id must be integer" ]`                                                                                                                                                                                                 |
| ERR\_INVALID\_DATA              | Invalid data. Lỗi này do data không phải là 1 chuỗi json string hợp lệ, thường do bạn không dùng các hàm json encode mà gõ thủ công gây thừa thiếu dấu hoặc không encode các kí tự đặc biệt. Bạn có thể kiểm tra chuỗi data json string bằng cách vào <https://jsonformatter.org> copy paste chuỗi của bạn vào textarea và click **Validate**. |
| ERR\_403                        | Access token không có quyền thao tác với dữ liệu, thường do user khi đăng nhập cấp quyền không chọn quyền này. VD để API lấy được danh sách đơn hàng, thì user phải có quyền truy cập trang danh sách đơn hàng trên Nhanh.vn, và phải chọn danh sách đơn hàng ở bước cấp quyền cho app.                                                        |
| ERR\_429                        | App của bạn đã vượt quá [API Rate Limit](#api-rate-limit)                                                                                                                                                                                                                                                                                      |

### POS common error codes

* Bảng bên dưới là các mã lỗi chung của POS. Từng API có thể có thêm các mã lỗi riêng, bạn xem tại mô tả Response của từng API.

### Vpage common error codes

| errorCode              | Description     |
| ---------------------- | --------------- |
| ERR\_PAGE\_EXPIRED     | Page expired    |
| ERR\_INVALID\_PAGE\_ID | Invalid page id |

### API Rate Limit

* Rate Limit là số lệnh gọi API mà ứng dụng của bạn có thể thực hiện trong khoảng thời gian nhất định. Mức mặc định là: **150 requests / 30 giây**. Nếu 1 API có mức riêng, thì tài liệu của API đó sẽ có mô tả riêng. Tùy theo lượng dữ liệu cần lấy, bạn cần tính toán cách xử lý để không bị lỗi Rate Limit. VD mỗi lần gọi API request bạn lấy về 100 bản ghi, thì tổng lượt gọi và số dữ liệu lấy được sẽ là:

| Thời gian | Lượt gọi API | Tổng số bản ghi |
| --------- | -----------: | --------------: |
| 30 giây   |          150 |          15.000 |
| 1 phút    |          300 |          30.000 |
| 1 giờ     |       18.000 |       1.800.000 |
| 1 ngày    |      432.000 |      43.200.000 |

* Rate Limit được kết hợp từ appId + businessId + API URL, nên ứng dụng của bạn có thể bị giới hạn ở 1 URL này, nhưng vẫn có thể gọi URL khác nếu URL đó không bị giới hạn, hoặc nếu ứng dụng của bạn dùng cho nhiều doanh nghiệp, thì có thể bị giới hạn với businessId 1, nhưng vẫn có thể dùng được với businessId 2. **Chú ý:** Nếu cố tình dùng nhiều ứng dụng để gọi liên tục API, hệ thống sẽ khóa toàn bộ các ứng dụng, sẽ không thể gọi bất kì API nào nữa.
* Lỗi này thường gặp phải khi ứng dụng không lưu dữ liệu mà luôn gọi API (VD liên tục gọi API lấy danh sách sản phẩm, danh sách đơn hàng...).
* **Khuyến cáo:** Bạn cần lưu dữ liệu ở hệ thống của bên bạn, cập nhật [dữ liệu mới từ webhooks](https://apidocs.nhanh.vn/v3/webhooks/webhooks), khi gọi API chỉ nên lấy các dữ liệu có thay đổi, không lấy toàn bộ dữ liệu cũ (các API có hỗ trợ lọc theo updatedAtFrom - updatedAtTo).
* **Nhanh Open API không hỗ trợ mở khóa Rate Limit**.
* Khi vượt quá Rate Limit, bạn sẽ nhận được errorCode = ERR\_429. Bạn cần tạm ngừng gọi API tới URL này cho tới khi quá thời gian **unlockedAt**. Nếu vẫn tiếp tục phát sinh gọi API khi đang bị Rate Limit, lockedSeconds và unlockedAt sẽ bị tăng lên.

```json
{
  "code": 0,
  "errorCode": "ERR_429",
  "message": "Your app exceeded the API Rate Limit",
  "data": {
    "lockedSeconds": 10,
    "unlockedAt": 1733387520
  }
}
```

| Key           | Description                                                                             |
| ------------- | --------------------------------------------------------------------------------------- |
| lockedSeconds | Số giây bị khóa                                                                         |
| unlockedAt    | Thời gian được mở khóa theo Unix timestamp. VD: 1733387520 = 2024-12-05 15:32:00 GMT+07 |
