Chào anh em! Khách dùng Nhanh.vn POS cho chuỗi cửa hàng, web chạy WooCommerce, nhờ bạn “nối API để tồn kho trên web khớp với Nhanh”. Đọc tài liệu Open API thì gặp ngay mấy chỗ dễ vấp: tài liệu vừa đổi tên miền, có hai phiên bản v2 và v3, token sống một năm mà không có refresh token, mỗi URL chỉ được 150 request trong 30 giây. Đọc hết bài, anh em tự tích hợp được API Nhanh.vn bằng PHP cho WordPress, từ tạo app tới code kéo sản phẩm chạy nền theo con trỏ next.

Endpoint, tham số và mã lỗi trong bài lấy từ tài liệu chính thức tại developers.nhanh.group (bản cũ apidocs.nhanh.vn vẫn mở), mình đọc ngày 11/10/2026; giao diện open.nhanh.vn kiểm cùng ngày và có thể đổi sau này. Plugin nhắc ở cuối bài là sản phẩm mình viết, dùng Open API của Nhanh.vn như mọi ứng dụng bên ngoài khác. Code bên dưới là mẫu tối giản để anh em nắm luồng, chạy thử trên bản sao website trước khi đưa lên thật. Trong các trang mình đọc, tài liệu không nhắc môi trường thử (sandbox) riêng, nên API ghi dữ liệu (thêm đơn, sửa sản phẩm) sẽ đụng vào dữ liệu thật của doanh nghiệp.
Nhanh.vn Open API là gì, tài liệu nằm ở đâu?
Open API là bộ API để hệ thống khác đồng bộ sản phẩm, đơn hàng, tồn kho, khách hàng, hoá đơn… với Nhanh.vn. Theo trang giới thiệu tài liệu, Open API hiện miễn phí và dùng được khi doanh nghiệp có gói dịch vụ của Nhanh. Mấy địa chỉ cần nhớ:
Để duy trì blog nên mình có làm aff cho 1 số bên hosting. Nhưng dù aff mình cũng chọn 1 số nhà cung cấp uy tín về chất lượng và support nên các bạn cứ yên tâm nhé.
Nếu có mua hosting mà có trong list dưới đây các bạn click vào link trước khi mua để ủng hộ mình nhé. Mình cảm ơn nhiều
- Azdigi: Giá rẻ thì dùng gói Pro Gold Hosting còn chất lượng hơn thì em khuyên dùng Business Hosting. Có điều kiện thì lên VPS nhé
- Tino hosting với mã giảm giá độc quyền lên tới 40%. Xem tại đây
- iNet
- Nước ngoài thì Vultr
- Tài liệu cho developer từ 23/09/2026 chuyển sang
developers.nhanh.group. Trang cũapidocs.nhanh.vnvẫn mở, có bản Markdown của từng trang (thêm.mdvào cuối đường dẫn) và mục lụcllms.txt, tiện để tra hoặc đưa cho công cụ AI đọc. - App được tạo và quản lý ở
open.nhanh.vn; tài khoản developer ở đây chỉ để tạo, sửa app, không cần quyền gì trên dữ liệu của shop. - Máy chủ API POS là
https://pos.open.nhanh.vn, còn Vpage có tên miền riêngvpage.open.nhanh.vn. - Nhanh có Postman collection v3.0 công khai, và mỗi trang tài liệu đều kèm lệnh curl mẫu để import thử.
Về phiên bản: Nhanh.vn API v3 ra mắt từ 31/08/2025; tài liệu ghi v2.0 đã ngừng phát triển tính năng mới và ngừng hỗ trợ sau 30/11/2026. Riêng phần webhook, form tạo app trên open.nhanh.vn (mình xem ngày 11/10/2026) ghi “version 2.0 (Ngừng hỗ trợ sau 31-08-2026)”. Việc mới thì viết thẳng theo v3, hệ thống đang chạy v2 thì lên lịch chuyển.
Các nhóm API v3 hay dùng khi nối website: product (list, detail, inventory, category, add, edit…), order (list, add, edit…), customer, business/depot (danh sách kho), shipping (địa chỉ, phí, hãng vận chuyển) và webhooks.
Bước 1: tạo app trên open.nhanh.vn
Đăng nhập open.nhanh.vn bằng tài khoản developer, vào Danh sách app, bấm Thêm mới. Các trường theo tài liệu và form hiện tại:
- Tên App hiện cho người dùng thấy lúc cấp quyền.
- Trạng thái chuyển sang Ngừng hoạt động thì app không gọi được API và không nhận webhook nữa.
- Redirect URL là đường dẫn trên hệ thống của bạn; người dùng cấp quyền xong, Nhanh chuyển về đây kèm
accessCode. Bắt buộc chạy https. - Nhóm ô Bật webhooks, Webhooks version, Webhooks callback URL và Webhooks verify token (16–128 ký tự gồm chữ và số) là phần nhận dữ liệu Nhanh gửi về website.

Phía doanh nghiệp, tài khoản Giám đốc phải bật “Cho phép kết nối Open API” ở Cài đặt chung → Cài đặt Open API trên Nhanh.vn; chưa bật thì API trả ERR_BUSINESS_NOT_ENABLE_API. Từng bước bấm, chỗ lấy App ID và Secret Key có ở bài lấy access token Nhanh.vn. Bật webhook, các sự kiện v3 và code nhận trong WordPress có ở bài webhook Nhanh.vn.
Bước 2: đổi accessCode lấy accessToken (v3)
Nhanh dùng luồng ba bước giống OAuth:
- Chuyển người dùng tới
https://nhanh.vn/oauth?appId=…&returnLink=….returnLinkphải chạy https và nằm trong Redirect URL của app. - Người dùng đăng nhập Nhanh.vn, chọn quyền (và kho) cấp cho app, bấm Đồng ý. Nhanh chuyển về
returnLink?accessCode=…. - Máy chủ của bạn gọi
POST https://pos.open.nhanh.vn/v3.0/app/getaccesstoken?appId=…với body JSON gồmaccessCodevàsecretKey. Phản hồi cóaccessToken,expiredAt(Unix timestamp),businessId,depotIds,pageIds,permissions.
- accessCode sống 10 phút và hết hiệu lực ngay khi đổi token thành công.
- ⚠️ accessToken hạn 1 năm kể từ lúc tạo, Nhanh chưa hỗ trợ refresh token. Hệ thống phải tự nhắc người dùng cấp quyền lại trước hạn.
- v3 chỉ giữ một token cho mỗi cặp appId + businessId: tạo token mới thì token cũ còn hiệu lực thêm 15 phút rồi thôi.
- Quyền và kho của token cố định lúc cấp. Tài khoản nhân viên chỉ chọn được những quyền tài khoản đó đang có; sau này được phân thêm quyền trên Nhanh thì token cũ vẫn không có, phải cấp quyền lại.
- Kiểm token bằng
/v3.0/app/checkaccesstoken: trả hạn, quyền, kho và page được thao tác.
Cấu hình để trong wp-config.php. DEVVN_NHANH_BUSINESS_ID để trống lần đầu cũng được; có ID doanh nghiệp rồi thì điền vào để code từ chối token của doanh nghiệp khác:
define( 'DEVVN_NHANH_APP_ID', 'YOUR_APP_ID' );
define( 'DEVVN_NHANH_SECRET_KEY', 'YOUR_SECRET_KEY' );
define( 'DEVVN_NHANH_BUSINESS_ID', '' );
Đoạn dưới gồm nút kết nối trong trang quản trị (kiểm quyền, nonce rồi đặt cờ chờ 10 phút) và route nhận accessCode, đổi token, lưu vào option không autoload:
/**
* Đường dẫn Nhanh.vn chuyển về sau khi cấp quyền; khai đúng chuỗi này ở Redirect URL của app.
*
* @return string
*/
function devvn_nhanh_return_link() {
return rest_url( 'devvn-nhanh/v1/oauth' );
}
/**
* Nút "Kết nối Nhanh.vn": kiểm quyền và nonce, đặt cờ chờ rồi chuyển sang trang cấp quyền của Nhanh.
*
* @return void
*/
function devvn_nhanh_connect() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Bạn không có quyền thực hiện thao tác này.' );
}
check_admin_referer( 'devvn_nhanh_connect' );
set_transient( 'devvn_nhanh_oauth_pending', get_current_user_id(), 10 * MINUTE_IN_SECONDS );
$url = 'https://nhanh.vn/oauth?' . http_build_query(
array(
'appId' => DEVVN_NHANH_APP_ID,
'returnLink' => devvn_nhanh_return_link(),
)
);
wp_redirect( $url );
exit;
}
add_action( 'admin_post_devvn_nhanh_connect', 'devvn_nhanh_connect' );
/**
* Đăng ký route GET nhận accessCode Nhanh.vn chuyển về.
*
* @return void
*/
function devvn_nhanh_register_oauth_route() {
register_rest_route(
'devvn-nhanh/v1',
'/oauth',
array(
'methods' => 'GET',
'callback' => 'devvn_nhanh_oauth_return',
'permission_callback' => '__return_true',
)
);
}
add_action( 'rest_api_init', 'devvn_nhanh_register_oauth_route' );
/**
* Đổi accessCode lấy accessToken v3, lưu token rồi chuyển về trang quản trị kèm kết quả.
*
* @param WP_REST_Request $request Request trình duyệt bị Nhanh.vn chuyển về.
* @return WP_REST_Response
*/
function devvn_nhanh_oauth_return( WP_REST_Request $request ) {
$code = sanitize_text_field( (string) $request->get_param( 'accessCode' ) );
$result = 'error';
if ( '' !== $code && get_transient( 'devvn_nhanh_oauth_pending' ) ) {
delete_transient( 'devvn_nhanh_oauth_pending' );
$res = wp_remote_post(
add_query_arg( 'appId', DEVVN_NHANH_APP_ID, 'https://pos.open.nhanh.vn/v3.0/app/getaccesstoken' ),
array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json' ),
'body' => wp_json_encode(
array(
'accessCode' => $code,
'secretKey' => DEVVN_NHANH_SECRET_KEY,
)
),
)
);
$json = is_wp_error( $res ) ? null : json_decode( wp_remote_retrieve_body( $res ), true );
if ( isset( $json['code'], $json['data']['accessToken'] ) && 1 === (int) $json['code'] ) {
$data = wp_parse_args(
$json['data'],
array(
'expiredAt' => 0,
'businessId' => 0,
'depotIds' => array(),
'permissions' => array(),
)
);
$want = (string) DEVVN_NHANH_BUSINESS_ID;
if ( '' === $want || (string) $data['businessId'] === $want ) {
update_option(
'devvn_nhanh_token',
array(
'token' => $data['accessToken'],
'expired_at' => (int) $data['expiredAt'],
'business_id' => (int) $data['businessId'],
'depot_ids' => $data['depotIds'],
'permissions' => $data['permissions'],
),
false
);
$result = 'ok';
}
}
}
return new WP_REST_Response( null, 302, array( 'Location' => add_query_arg( 'devvn_nhanh', $result, admin_url() ) ) );
}
- Nút kết nối là một link tới
wp_nonce_url( admin_url( 'admin-post.php?action=devvn_nhanh_connect' ), 'devvn_nhanh_connect' ), đặt ở trang cài đặt của bạn. - Redirect URL khai trên app phải đúng chuỗi
devvn_nhanh_return_link()trả về, ví dụhttps://ten-mien-cua-ban/wp-json/devvn-nhanh/v1/oauth. - Route phải công khai vì trình duyệt bị Nhanh chuyển về, không mang nonce REST. Mình chặn bằng hai lớp: cờ chờ chỉ admin bấm nút mới đặt được (sống 10 phút, bằng tuổi accessCode), và so
businessIdtrả về với ID doanh nghiệp đã điền. Tài liệu không nhắc tham sốstatenhư OAuth chuẩn, nên đừng dựa vào nó. - secretKey và accessToken chỉ nằm ở máy chủ: không đưa vào JavaScript, không ghi ra log, không commit lên Git.
- Lưu
expired_atđể nhắc người dùng cấp quyền lại trước hạn, đừng đợi tới lúc API báo lỗi token.
Bước 3: quy ước gọi Nhanh.vn API v3
Mọi API v3 dùng POST với body JSON thô; appId và businessId gắn trên đường dẫn, accessToken nằm ở header Authorization (ví dụ trong tài liệu gửi nguyên token, không có tiền tố Bearer). Phản hồi luôn có trường code:
"code": 1: thành công, kèmdata; tuỳ API có thêmpaginatorvàwarning(ví dụ báo trường sắp bỏ)."code": 0: lỗi, kèmerrorCode,messages(chuỗi, hoặc mảng theo từng trường) và có thể códata.
| errorCode | Ý nghĩa, cách xử lý |
|---|---|
ERR_INVALID_APP_ID | appId sai. |
ERR_INVALID_ACCESS_TOKEN | Token sai, hết hạn hoặc đã bị token mới thay. |
ERR_INVALID_VERSION | Lấy token v2 gọi API v3. Cấp quyền lại để có token v3. |
ERR_INVALID_BUSINESS_ID | businessId sai. |
ERR_INVALID_DEPOT_ID | Lúc cấp quyền không chọn kho này. Cấp lại, chọn đủ kho. |
ERR_BUSINESS_NOT_ENABLE_API | Doanh nghiệp chưa bật Cho phép kết nối Open API. |
ERR_INVALID_FORM_FIELDS | Trường dữ liệu sai; messages chỉ rõ trường nào. |
ERR_INVALID_DATA | Body không phải JSON hợp lệ, thường do tự ghép chuỗi thay vì dùng hàm encode. |
ERR_403 | Token không có quyền với dữ liệu này. Cấp lại và chọn quyền đó. |
ERR_429, ERR_EXCEEDED_RATE_LIMIT | Vượt rate limit, xem mục dưới. |
Rẽ nhánh theo errorCode, đừng so câu chữ trong messages. Cần kỹ thuật Nhanh hỗ trợ API thì theo trang Hỗ trợ kỹ thuật: gửi email dev@nhanh.vn kèm tên doanh nghiệp, businessId và Code Snippet dạng cURL xuất từ Postman (nhớ che token).
Rate limit 150 request/30 giây và lỗi ERR_429
- Mức mặc định 150 request trong 30 giây, tính theo appId + businessId + URL API. Bị khoá ở URL này vẫn gọi được URL khác; app dùng cho nhiều doanh nghiệp thì khoá theo từng doanh nghiệp. API nào có mức riêng thì trang của API đó ghi rõ.
- Vượt mức thì nhận
errorCode: ERR_429kèmdata.lockedSeconds(số giây bị khoá) vàdata.unlockedAt(thời điểm mở khoá, Unix timestamp). - 🚨 Gọi tiếp khi đang bị khoá làm
lockedSecondsvàunlockedAttăng thêm. Nhanh không mở khoá theo yêu cầu, và cố tình dùng nhiều app để gọi dồn thì toàn bộ các app bị khoá. - Khuyến cáo của tài liệu: lưu dữ liệu ở hệ thống của bạn, nhận thay đổi qua webhook, khi gọi API chỉ lấy phần thay đổi bằng
updatedAtFrom/updatedAtTo.
{
"code": 0,
"errorCode": "ERR_429",
"message": "Your app exceeded the API Rate Limit",
"data": {
"lockedSeconds": 10,
"unlockedAt": 1733387520
}
}
Để ý mẫu lỗi này dùng message (số ít) chứ không phải messages. Tài liệu cũng không ghi mã HTTP đi kèm, nên hàm gọi chung dưới đây bắt cả errorCode lẫn HTTP 429. Hàm đọc token đã lưu ở bước 2, trả mảng phản hồi khi code bằng 1, còn lại trả WP_Error có mã là errorCode:
define( 'DEVVN_NHANH_API_HOST', 'https://pos.open.nhanh.vn/' );
define( 'DEVVN_NHANH_TIMEOUT', 15 );
define( 'DEVVN_NHANH_DEFAULT_LOCK', 10 );
/**
* Gọi một API v3 của Nhanh.vn; URL đang bị khoá rate limit thì trả lỗi ngay, không gọi sang Nhanh.
*
* @param string $path Đường dẫn API, ví dụ 'v3.0/product/list'.
* @param array $body Body JSON gửi đi.
* @return array|WP_Error Phản hồi khi code = 1, hoặc lỗi mang mã errorCode (lỗi 429 có retry_after).
*/
function devvn_nhanh_request( $path, array $body = array() ) {
$auth = get_option( 'devvn_nhanh_token', array() );
if ( empty( $auth['token'] ) ) {
return new WP_Error( 'nhanh_no_token', 'Chưa có accessToken, cần kết nối lại Nhanh.vn.' );
}
$lock_key = 'devvn_nhanh_lock_' . md5( $path );
$unlock = (int) get_transient( $lock_key );
if ( $unlock > time() ) {
return new WP_Error( 'ERR_429', 'URL đang bị khoá rate limit.', array( 'retry_after' => $unlock - time() ) );
}
$url = add_query_arg(
array(
'appId' => DEVVN_NHANH_APP_ID,
'businessId' => $auth['business_id'],
),
DEVVN_NHANH_API_HOST . ltrim( $path, '/' )
);
$res = wp_remote_post(
$url,
array(
'timeout' => DEVVN_NHANH_TIMEOUT,
'headers' => array(
'Authorization' => $auth['token'],
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( empty( $body ) ? new stdClass() : $body ),
)
);
if ( is_wp_error( $res ) ) {
return $res;
}
$http = (int) wp_remote_retrieve_response_code( $res );
$json = json_decode( wp_remote_retrieve_body( $res ), true );
if ( ! is_array( $json ) ) {
return new WP_Error( 'nhanh_http_' . $http, 'Phản hồi không phải JSON.', array( 'status' => $http ) );
}
if ( isset( $json['code'] ) && 1 === (int) $json['code'] ) {
return $json;
}
$error = isset( $json['errorCode'] ) ? (string) $json['errorCode'] : 'nhanh_http_' . $http;
if ( 429 === $http || in_array( $error, array( 'ERR_429', 'ERR_EXCEEDED_RATE_LIMIT' ), true ) ) {
$wait = isset( $json['data']['lockedSeconds'] ) ? (int) $json['data']['lockedSeconds'] : DEVVN_NHANH_DEFAULT_LOCK;
$until = isset( $json['data']['unlockedAt'] ) ? (int) $json['data']['unlockedAt'] : time() + $wait;
$until = max( $until, time() + 1 );
set_transient( $lock_key, $until, $until - time() );
return new WP_Error( 'ERR_429', 'Nhanh.vn đang khoá rate limit.', array( 'retry_after' => $until - time() ) );
}
$msg = isset( $json['messages'] ) ? $json['messages'] : ( isset( $json['message'] ) ? $json['message'] : '' );
return new WP_Error( $error, is_array( $msg ) ? wp_json_encode( $msg ) : (string) $msg, array( 'status' => $http ) );
}
- Đang bị khoá thì hàm không gọi sang Nhanh mà trả lỗi ngay, để khoá không bị kéo dài. Lỗi mang
retry_after(giây) để job chạy nền hẹn lại đúng lúc. - Request từ trang sản phẩm, giỏ hàng gặp ERR_429 thì dùng dữ liệu đã lưu để khách khỏi phải chờ; chỉ job nền mới hẹn gọi lại.
- Body rỗng gửi
{}, không gửi[]. Body là mảng danh sách (nhưproduct/addnhận mảng sản phẩm) thì giữ nguyên dạng mảng. - Timeout đặt rõ 15 giây vì mặc định của
wp_remote_post()chỉ 5 giây, dễ cắt ngang các API trả nhiều dữ liệu. - Muốn chủ động hơn thì tự đếm request theo URL và giữ dưới mức 150/30 giây, thay vì đợi tới lúc bị khoá.
Phân trang bằng paginator.next: kéo hết sản phẩm không sót
- Request gửi
paginatorgồmsize(mặc định tối đa 100, API nào cho lớn hơn thì ghi riêng),sort(vớiproduct/listlà sắp theoid,aschoặcdesc) vànext. - Trang đầu không gửi
next. Các trang sau gửi lại đúng cấu trúcnextvừa nhận: nhận{"next":{"id":"xxx"}}thì gửi"paginator":{"next":{"id":"xxx"}}, không rút gọn thành"next":"xxx". Mỗi API, thậm chí mỗi trang, có thể trảnextkhác kiểu. - Hết dữ liệu khi không có
nexthoặcnextlà null. Trang trả ít bản ghi hơnsizemà vẫn cónextthì vẫn còn trang sau (dữ liệu bị xoá hoặc bị lọc ẩn). product/listtối đa 100 sản phẩm mỗi trang, lọc được theoids(tối đa 100),name,parentId,gpid,categoryIds,status,price,brandIds,imei,updatedAtFrom,updatedAtTo.- ⚠️
updatedAtchỉ đổi khi thông tin sản phẩm (tên, giá…) đổi, không tính thay đổi tồn kho. Tồn kho phải nhận qua webhookinventoryChange. Tồn trả về cũng chỉ gồm các kho token được cấp, nên thấy thiếu tồn so với giao diện Nhanh thì kiểm lại kho đã chọn lúc cấp quyền.
Đoạn dưới kéo sản phẩm có thay đổi theo kiểu chạy nền: Action Scheduler (đi kèm WooCommerce) gọi 15 phút một lần, mỗi lượt chạy tối đa 20 giây, con trỏ next và mốc thời gian được nhớ trong option để lượt sau chạy tiếp. Quét xong một vòng thì mốc cuối thành updatedAtFrom của vòng sau, nên chỉ vòng khởi đầu mới đi hết danh sách. Sản phẩm trên web khớp với Nhanh qua meta _devvn_nhanh_id:
define( 'DEVVN_NHANH_META', '_devvn_nhanh_id' );
define( 'DEVVN_NHANH_SYNC_HOOK', 'devvn_nhanh_sync_products' );
define( 'DEVVN_NHANH_SYNC_INTERVAL', 15 * MINUTE_IN_SECONDS );
define( 'DEVVN_NHANH_SYNC_BUDGET', 20 );
define( 'DEVVN_NHANH_PAGE_SIZE', 100 );
/**
* Hẹn lịch kéo sản phẩm định kỳ bằng Action Scheduler; chỉ kiểm ở trang quản trị cho nhẹ trang khách.
*
* @return void
*/
function devvn_nhanh_schedule_sync() {
if ( function_exists( 'as_has_scheduled_action' ) && ! as_has_scheduled_action( DEVVN_NHANH_SYNC_HOOK ) ) {
as_schedule_recurring_action( time(), DEVVN_NHANH_SYNC_INTERVAL, DEVVN_NHANH_SYNC_HOOK );
}
}
add_action( 'admin_init', 'devvn_nhanh_schedule_sync' );
/**
* Kéo các trang sản phẩm có thay đổi trong giới hạn thời gian, nhớ con trỏ next để lượt sau chạy tiếp.
*
* @return void
*/
function devvn_nhanh_sync_products() {
$state = wp_parse_args(
get_option( 'devvn_nhanh_product_sync', array() ),
array(
'from' => 0,
'to' => 0,
'next' => null,
)
);
$state['to'] = $state['to'] ? (int) $state['to'] : time();
$deadline = time() + DEVVN_NHANH_SYNC_BUDGET;
do {
$filters = array( 'updatedAtTo' => (int) $state['to'] );
if ( $state['from'] ) {
$filters['updatedAtFrom'] = (int) $state['from'];
}
$paginator = array(
'size' => DEVVN_NHANH_PAGE_SIZE,
'sort' => array( 'id' => 'asc' ),
);
if ( ! empty( $state['next'] ) ) {
$paginator['next'] = $state['next'];
}
$res = devvn_nhanh_request(
'v3.0/product/list',
array(
'filters' => $filters,
'paginator' => $paginator,
)
);
if ( is_wp_error( $res ) ) {
wc_get_logger()->warning( $res->get_error_code() . ': ' . $res->get_error_message(), array( 'source' => 'devvn-nhanh' ) );
break;
}
devvn_nhanh_save_products( isset( $res['data'] ) ? (array) $res['data'] : array() );
$state['next'] = isset( $res['paginator']['next'] ) ? $res['paginator']['next'] : null;
if ( empty( $state['next'] ) ) {
$state = array(
'from' => $state['to'],
'to' => 0,
'next' => null,
);
break;
}
} while ( time() < $deadline );
update_option( 'devvn_nhanh_product_sync', $state, false );
}
add_action( DEVVN_NHANH_SYNC_HOOK, 'devvn_nhanh_sync_products' );
/**
* Tìm ID sản phẩm, biến thể WooCommerce theo ID Nhanh.vn bằng một truy vấn.
*
* @param array $nhanh_ids Danh sách ID sản phẩm trên Nhanh.vn.
* @return array Mảng ID Nhanh => ID WooCommerce.
*/
function devvn_nhanh_map_products( array $nhanh_ids ) {
if ( empty( $nhanh_ids ) ) {
return array();
}
$post_ids = get_posts(
array(
'post_type' => array( 'product', 'product_variation' ),
'post_status' => 'any',
'fields' => 'ids',
'posts_per_page' => -1,
'no_found_rows' => true,
'meta_query' => array(
array(
'key' => DEVVN_NHANH_META,
'value' => array_map( 'strval', $nhanh_ids ),
'compare' => 'IN',
),
),
)
);
update_meta_cache( 'post', $post_ids );
$map = array();
foreach ( $post_ids as $post_id ) {
$map[ (string) get_post_meta( $post_id, DEVVN_NHANH_META, true ) ] = (int) $post_id;
}
return $map;
}
/**
* Ghi giá bán lẻ của một trang sản phẩm Nhanh.vn vào các sản phẩm WooCommerce đã khớp ID.
*
* @param array $rows Phần data của /v3.0/product/list.
* @return void
*/
function devvn_nhanh_save_products( array $rows ) {
$rows = array_column( $rows, null, 'id' );
$map = devvn_nhanh_map_products( array_keys( $rows ) );
_prime_post_caches( array_values( $map ), false, true );
foreach ( $map as $nhanh_id => $post_id ) {
$product = wc_get_product( $post_id );
$price = isset( $rows[ $nhanh_id ]['prices']['retail'] ) ? (float) $rows[ $nhanh_id ]['prices']['retail'] : 0;
if ( ! $product || $product->is_type( 'variable' ) || $price <= 0 || (float) $product->get_regular_price() === $price ) {
continue;
}
$product->set_regular_price( wc_format_decimal( $price ) );
$product->save();
}
}
- Lần khớp sản phẩm ban đầu anh em có thể ghép theo SKU (trường
codecủa Nhanh) rồi lưu ID Nhanh vào meta_devvn_nhanh_id; từ đó mọi lượt đều tra theo ID. - Cả trang 100 sản phẩm tra bằng một truy vấn meta, nạp sẵn cache rồi mới đọc, nên không có truy vấn nào trong vòng lặp. Chỉ sản phẩm có giá thay đổi mới bị ghi.
- Với biến thể, code bỏ qua dòng cha (Nhanh dùng
parentId-2 cho sản phẩm cha) và ghi giá vào từng biến thể. - Gặp lỗi (kể cả ERR_429) thì job chỉ ghi log ở WooCommerce → Trạng thái → Nhật ký, nguồn
devvn-nhanh, rồi dừng lượt; lượt sau chạy tiếp từ con trỏ cũ. Xử lý lại một trang đã làm cũng không sinh dữ liệu sai, nên chạy lại là vô hại. - Muốn kéo thêm ảnh, mô tả, danh mục thì viết tiếp trong
devvn_nhanh_save_products(). Tồn kho để webhook lo.
Chuyển từ API v2 sang v3: những gì phải sửa
| Mục | v2.0 | v3.0 |
|---|---|---|
| Gửi request | Body form-data, token nằm trong body | Body JSON thô, token ở header Authorization |
| Đổi token | /api/oauth/access_token | /v3.0/app/getaccesstoken |
| Verify token webhook | Trong body (webhooksVerifyToken) | Header Authorization |
| Gọi từ JavaScript | Bị chặn | Đã mở CORS |
| Thêm đơn | Nhận ID sản phẩm của web và idNhanh | Chỉ dùng ID sản phẩm trên Nhanh |
- Bảng mã lỗi hiện tại ghi lấy token v2 gọi API v3 sẽ ra
ERR_INVALID_VERSION, nên chuyển sang v3 là phải cấp quyền lại để có token v3. Trang “Nâng cấp lên v3.0” viết từ thời v3 Alpha còn nói token v2 dùng được cho v3; mình theo bảng mã lỗi. - Cấu trúc dữ liệu trả về (response) đổi hoàn toàn, phải đọc lại tài liệu từng API. Paginator mới hỗ trợ nhiều kiểu sắp xếp hơn.
- Khoảng thời gian lọc ở một số API trước giới hạn 10 ngày, v3 nâng lên 31 ngày.
- Với webhook, đổi Webhooks version trong app sang 3.0 và sửa code đọc verify token từ header. Payload v3 có cấu trúc giống dữ liệu của API danh sách nên đỡ phải gọi thêm.
Tự viết hay dùng plugin cho WooCommerce?
Gọi được API mới là phần dễ. Phần tốn công là mọi thứ xung quanh: giao diện kết nối và nhắc hạn token, khớp sản phẩm, biến thể, ảnh, hàng đợi webhook, điều tiết request, đăng đơn, phí ship ở trang thanh toán…
- Tự viết hợp khi hệ thống không phải WooCommerce, hoặc chỉ cần một luồng lẻ (ví dụ đẩy đơn từ landing page sang Nhanh).
- Dùng plugin hợp khi web là WooCommerce và cần chạy ngay.
Với plugin kết nối Nhanh.vn với WooCommerce của mình, những phần trong bài đã có sẵn:
- Để kết nối, tab “Nhanh.vn” cho chọn Version 2 hoặc Version 3 và đưa sẵn Redirect URL, Webhooks callback URL để dán vào app; anh em nhập Business ID, App ID, Secret Key rồi bấm “Get accessCode” ở bước 9. Khung kết nối hiện dòng “Token hết hiệu lực vào” để biết lúc nào cần cấp lại; plugin không tự gia hạn token.
- Plugin tự điều tiết API, giữ khoảng 120 trên 150 request mỗi 30 giây cho mỗi endpoint; gặp ERR_429 thì chờ hết khoá rồi gọi lại một lần.
- Plugin nhận webhook, lưu vào hàng đợi, trả 200 rồi mới xử lý trong nền; trang Webhook Queue cho xem Chờ xử lý, Hoàn thành, Thất bại và bấm Retry.
- Phần đồng bộ sản phẩm v3 lấy danh mục, sản phẩm, biến thể, ảnh (nhận theo phiên bản) và có chế độ đồng bộ trong nền. Đồng bộ một chiều từ Nhanh về web, không xoá sản phẩm trên web.
- Dev có các hook như
pre_data_create_order_v3,nhanhvn_webhook_defaultđể tuỳ biến mà không sửa lõi. Danh sách ở trang hooks của plugin Nhanh.vn.

Phần kết nối trong plugin có hướng dẫn chi tiết ở trang kết nối API Nhanh.vn của tài liệu plugin. So sánh ba cách nối Nhanh.vn với website (website của Nhanh, tự code, plugin) có ở bài kết nối Nhanh.vn với website.
Câu hỏi thường gặp
Open API Nhanh.vn có tính phí không?
Theo trang giới thiệu tài liệu (đọc 10/2026), Open API hiện miễn phí; doanh nghiệp mua gói dịch vụ của Nhanh thì được dùng Open API.
Có môi trường sandbox để test không?
Trong các trang tài liệu mình đọc không thấy môi trường thử riêng. Nên thử các API đọc trước (product/list, business/depot), dùng Postman collection của Nhanh để xem tham số, và chỉ chạy API ghi khi đã chắc dữ liệu gửi đi.
Gọi API Nhanh.vn bằng JavaScript ở trình duyệt được không?
Tài liệu ghi v3 đã mở CORS. Nhưng gọi từ trình duyệt là lộ accessToken cho bất kỳ ai xem mã trang, và token này sống tới một năm. Với website, hãy gọi từ máy chủ như các đoạn PHP ở trên.
Token v2 có gọi được API v3 không?
Không. Theo bảng mã lỗi, lấy token v2 gọi API v3 sẽ nhận ERR_INVALID_VERSION. Cấp quyền lại để lấy token v3; với plugin là chọn Version 3 rồi bấm Get accessCode lần nữa.
Anh em tích hợp vướng chỗ nào cứ để lại bình luận, nhớ che accessToken, Secret Key và businessId trước khi dán log nhé. Chúc vòng quét khởi đầu đi hết danh sách sản phẩm mà không dính lần ERR_429 nào! 🚀
Bài viết trong series
Kết nối WooCommerce với Nhanh.vn
0/8đã đọc- 1 Lấy access token Nhanh.vn: tạo app, App ID, Secret Key và gia hạn Đang đọc Đã đọc
- 2 Kết nối Nhanh.vn với website: 3 cách và 5 bước nối WooCommerce Đang đọc Đã đọc
- 3 Đồng bộ tồn kho Nhanh.vn với website WooCommerce qua webhook Đang đọc Đã đọc
- 4 Đồng bộ sản phẩm Nhanh.vn về website WooCommerce trong 3 bước Đang đọc Đã đọc
- 5 Đăng đơn lên Nhanh.vn từ WooCommerce: thủ công, hàng loạt, tự động Đang đọc Đã đọc
- 6 Hiển thị tồn kho theo cửa hàng WooCommerce bằng dữ liệu Nhanh.vn Đang đọc Đã đọc
- 7 Webhook Nhanh.vn v3: cài đặt, xác thực và code nhận trong WordPress Đang đọc Đã đọc
- 8 Tích hợp Nhanh.vn Open API v3 bằng PHP cho WordPress, WooCommerce Đang đọc Đã đọc





