Chuyển đến nội dung chính

Callback

Tổng quan

Callback hoặc Webhook là một cơ chế tự động thông báo cho các nhà bán hàng về các sự kiện liên quan đến giao dịch Zalopay. Người dùng và nhà phát triển bên thứ ba có thể quản lý các Callback này. Chúng là các phản hồi HTTP được xác định bởi người dùng cho phép hai ứng dụng trực tuyến độc lập giao tiếp với nhau.

Luồng thực hiện

Khi một giao dịch cụ thể thành công:

  • Máy chủ Zalopay sẽ thông báo đến Máy chủ Merchant thông qua Callback URL đã đăng ký trước đó, ví dụ CreateOrder API.
  • Máy chủ Merchant sử dụng key2 (do Zalopay cung cấp) để xác nhận dữ liệu callback.
  • Merchant có thể kiểm tra callback log trong môi trường Sandbox trên Merchant Portal

Request

Các điểm cuối gọi lại (callback endpoints) của bạn phải có thể truy cập được bởi máy chủ Zalopay.

  • Phương thức: POST
  • Content-Type: application/json

Điểm cuối này nên chấp nhận dữ liệu theo cấu trúc sau (áp dụng cho tất cả sản phẩm):

Tham sốKiểu dữ liệuMô tả
dataJson StringDữ liệu gọi lại (callback data) có thể thay đổi định dạng tùy thuộc vào sản phẩm cụ thể.
macstringXác nhận thông tin đơn hàng bằng cách sử dụng key2 (đã được cung cấp) để xác minh đơn hàng.
typeintLoại gọi lại (callback type):
- type=1: Order
- type=2: Agreement

Dữ liệu gọi lại (callback data) cho sản phẩm cụ thể được trình bày như sau:

Dữ liệu gọi lại (callback data) cho đơn hàng (Order) là như sau:
Tham sốKiểu dữ liệuMô tả
app_idIntapp_id của đơn hàng
app_trans_idStringapp_trans_id của đơn hàng
app_timeInt64app_time của đơn hàng
app_userStringapp_user của đơn hàng
amountInt64Số tiền đã nhận được (VND)
embed_dataJson Stringembed_data của đơn hàng.
Ví dụ: {"promotioninfo":"","merchantinfo":"merchant-defined data"}
itemJson ArrayOitem của đơn hàng.
Ví dụ: [{"itemid":"knb","itename":"plantA","itemprice":198400,"itemquantity":1}]
zp_trans_idInt64Mã giao dịch của Zalopay.
server_timeInt64Thời gian giao dịch của Zalopay (thời gian Unix dạng timestamp tính bằng millisecond).
channelIntKênh thanh toán.
merchant_user_idStringNgười dùng Zalopay, người đã thanh toán cho đơn hàng.
user_fee_amountInt64Số tiền phí (VND)
discount_amountInt64Số tiền giảm giá (VND)

Example:

{
"data": "{\"app_id\":2638,\"app_trans_id\":\"230407_13583500399\",\"app_time\":1680850715576,\"app_user\":\"Zalopay\",\"amount\":50000,\"embed_data\":\"{}\",\"item\":\"[]\",\"zp_trans_id\":230407000006575,\"server_time\":1680850894407,\"channel\":38,\"merchant_user_id\":\"Bs8KJXf2GKyfVv_fnxG7IwG5VbQ8H_qGsOUW2UJAJSM\",\"zp_user_id\":\"Bs8KJXf2GKyfVv_fnxG7IwG5VbQ8H_qGsOUW2UJAJSM\",\"user_fee_amount\":0,\"discount_amount\":0}",
"mac": "761c9be42fd419b7078e4cf42e5034238ced266b2e094bfd4ce242f99e61b172",
"type": 1
}
Dữ liệu gọi lại (callback data) cho quá trình thanh toán sử dụng token/liên kết hợp đồng (agreement) là như sau:

Dữ liệu gọi lại (callback data) bao gồm chi tiết về các thỏa thuận được xác nhận hoặc cập nhật.

Tham sốKiểu dữ liệuMô tả
app_idIntApp ID của người bán.
app_trans_idStringID duy nhất của người bán để ràng buộc (binding).
binding_idStringID của quá trình ràng buộc (binding) đã được xác nhận trong hệ thống Zalopay.
pay_tokenStringToken công khai (public token) được sử dụng khi thực hiện tự động trừ tiền (auto-debit).
server_timeIntThời trên máy chủ (server timestamp) tính bằng giây.
merchant_user_idStringTrường thông tin định danh (identifier field) trong yêu cầu ràng buộc (bind request).
statusIntLoại tin nhắn (message type):
1: Người dùng xác nhận một thỏa thuận
2: Người dùng cập nhật thỏa thuận
msg_typeInt1: Thành công, còn lại là thất bại.
zp_user_idStringĐịnh danh của người dùng Zalopay theo app_id của người bán.
masked_user_phoneStringSố điện thoại của người dùng được che giấu (Ex: masked_user_phone: "****6938").

Ví dụ:

{
"data": "{\"app_id\":2638,\"app_trans_id\":\"230407_13221300383\",\"binding_id\":\"230407qQe7vGnqp0agyforLAy0D2b1x3\",\"pay_token\":\"NJLHNTRLZDETZGQYZI0ZNTBKLTGWZJKTMTKXYJHLMWI5NTFI\",\"merchant_user_id\":\"84903863801\",\"zp_user_id\":\"Bs8KJXf2GKyfVv_fnxG7IwG5VbQ8H_qGsOUW2UJAJSM\",\"masked_user_phone\":\"****3801\",\"server_time\":1680848564,\"status\":1,\"msg_type\":1,\"expiry_timestamp_in_ms\":1996467763977}",
"mac": "bed74dc38e023d7402297107e1e518b16731d1689ebf77120872e12d9e36882a",
"type": 2
}
Dữ liệu gọi lại (callback data) cho ZOD là như sau:
Tham sốKiểu dữ liệuMô tả
appIdStringBộ nhận dạng ứng dụng (app identifier) được cung cấp bởi Zalopay.
mcRefIdStringID tham chiếu của đơn hàng của người bán (merchant order's reference ID).
amountIntSố tiền của đơn hàng.
zpTransIdIntID giao dịch của Zalopay (Zalopay transaction ID).
serverTimeIntThời gian giao dịch của Zalopay (thời gian Unix dạng timestamp tính bằng millisecond).
channelIntKênh thanh toán.
zpUserIdStringĐịnh danh của người dùng Zalopay theo từng người bán
userFeeAmountIntSố tiền phí (VND)
discountAmountIntSố tiền giảm giá (VND)
userChargeAmountIntSố tiền được thu từ người dùng.

Example:

{
"data": "{\"appId\":\"15011\",\"mcRefId\":\"LZD201230_23423453\",\"amount\":30000,\"zpTransId\":210126000000814,\"serverTime\":1611633042737,\"channel\":38,\"zpUserId\":\"200601589000506\",\"userFeeAmount\":0,\"discountAmount\":0,\"userChargeAmount\":30000}",
"mac": "dd163f1bc82a92eb8c9619337f47298fee0c95b1c764767ad18f358f0e8120a9",
"type": 1
}

Phản hồi (response)

Tham sốKiểu dữ liệuMô tả
return_codeInt1: Thành công
2: Thất bại
return_messageStringMô tả về mã trả về

Đối với sản phẩm ZOD, định dạng phản hồi dự kiến như sau:

Tham sốKiểu dữ liệuMô tả
returnCodeInt1: Thành công
2: Thất bại
returnMessageStringMô tả về mã trả về

Cài đặt

Xác thực (validation)

URL callback của bạn có thể bị tấn công bởi hacker. Để đảm bảo tính hợp lệ của một callback, việc xác thực bằng HMAC là bắt buộc.

reqmac = HMAC(hmac_algorithm, key2, callback_data.data);
if (reqmac == callback_data.mac) {
// valid callback
} else {
// invalid callback
}

trong đó:

  • hmac_algorithm: là phương pháp bảo mật được đăng ký bởi Merchant với Zalopay, giá trị mặc định là HmacSHA256
  • key2: được Zalopay cung cấp trong quá trình đăng ký.
  • callback_data: là dữ liệu mà Zalopay yêu cầu từ API gọi lại của Merchant khi Zalopay đã thành công thu tiền từ khách hàng.

Ví dụ

// Node v10.15.3
const CryptoJS = require("crypto-js"); // npm install crypto-js
const express = require("express"); // npm install express
const bodyParser = require("body-parser"); // npm install body-parser
const app = express();

const config = {
key2: "eG4r0GcoNtRGbO8",
};

app.use(bodyParser.json());

app.post("/callback", (req, res) => {
let result = {};

try {
let dataStr = req.body.data;
let reqMac = req.body.mac;

let mac = CryptoJS.HmacSHA256(dataStr, config.key2).toString();
console.log("mac =", mac);

// check if the callback is valid (from Zalopay server)
if (reqMac !== mac) {
// callback is invalid
result.return_code = -1;
result.return_message = "mac not equal";
} else {
// payment success
// merchant update status for order's status
let dataJson = JSON.parse(dataStr, config.key2);
console.log(
"update order's status = success where app_trans_id =",
dataJson["app_trans_id"]
);

result.return_code = 1;
result.return_message = "success";
}
} catch (ex) {
result.return_code = 0; // callback again (up to 3 times)
result.return_message = ex.message;
}

// returns the result for Zalopay server
res.json(result);
});

app.listen(8888, function () {
console.log("Server is listening at port :8888");
});

Để xem các ví dụ khác, vui lòng truy cập vào đường dẫn sau: https://github.com/orgs/zalopay-samples/repositories

Đề xuất

Thử lại (Retry)

Do các vấn đề kỹ thuật như mất kết nối mạng hoặc không khả dụng dịch vụ, Zalopay có thể không thể thông báo cho bạn thông qua callback. Sau 15 phút kể từ thời điểm thiết lập đơn hàng, nếu bạn vẫn không nhận được callback từ Zalopay, người bán cần gọi API QueryOrder API một cách chủ động để lấy kết quả cuối cùng.

Idempotence (Tính đơn nhất)

Các điểm cuối callback có thể nhận được các sự kiện trùng lặp, điều này có thể gây ra vấn đề nếu các sự kiện được xử lý nhiều lần. Để đảm bảo rằng các sự kiện trùng lặp này không gây ra vấn đề, việc xử lý sự kiện cần phải đơn nhất (idempotent). Điều này có nghĩa là việc xử lý cùng một sự kiện nhiều lần sẽ có cùng kết quả như việc xử lý nó chỉ một lần. Một cách để đạt được điều này là theo dõi các sự kiện đã được xử lý và tránh xử lý lại các sự kiện đã được theo dõi.

Sử dụng HTTPS

Bằng cách sử dụng URL HTTPS cho điểm cuối callback, bạn có thể đảm bảo rằng kết nối giữa máy khách và máy chủ là an toàn và được mã hóa, cung cấp bảo vệ chống lại các mối đe dọa an ninh tiềm ẩn. Với HTTPS, dữ liệu được truyền giữa máy chủ và máy khách được mã hóa, ngăn chặn kẻ tấn công từ việc chặn và đọc thông tin nhạy cảm.

Bảo vệ chống lại CSRF

Các framework web như Rails, Django hoặc các framework khác có thể tự động xác minh rằng mỗi yêu cầu POST bao gồm mã thông báo CSRF (CSRF token) nhằm bảo vệ chống lại các cuộc tấn công giả mạo yêu cầu xuyên trang. Mặc dù đây là một tính năng bảo mật quan trọng để bảo vệ bạn và người dùng của bạn, nhưng nó có thể gây trở ngại trong việc xử lý các sự kiện hợp lệ trên trang web của bạn. Trong những trường hợp như vậy, bạn có thể loại trừ đường dẫn callback khỏi bảo vệ CSRF.

Ví dụ với Express.js:

const express = require("express");
const cookieParser = require("cookie-parser");
const csrf = require("csurf");
const app = express();

// Set up middleware
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

const protectCSRF = csrf({ cookie: true });

// Set up CSRF protection, excluding your callback path
app.use((req, res, next) => {
if (req.url === "your_callback_path") {
next();
} else {
protectCSRF(req, res, next);
}
});

// Do some action to create csrf token and attach to the cookie here

app.listen(8080);

Khác

Kênh thanh toán

Giá trịKênh Thanh Toán
36Visa/Master/JCB
37Bank Account
38Zalopay Wallet
39ATM
41Visa/Master Debit