Token Ví
Tổng quan
Thanh toán tự động (Auto Debit) là một giải pháp thanh toán cho phép người bán có thể tự động trừ tiền vào số dư, tài khoản của người dùng sau khi người dùng đăng ký một hợp đồng (agreement).
Trong các phần tiếp theo, chúng tôi sẽ hướng dẫn bạn từng bước để tích hợp Zalopay. Bạn có thể dùng thử ứng dụng demo của chúng tôi để có cái nhìn tổng quan. Một triển khai ví dụ hoàn chỉnh (với NextJs) cũng được cung cấp tại Github repository.
Tiền điều kiện
Trước khi bạn bắt đầu, hãy đảm bảo các công việc sau được thực hiện để tích hợp suôn sẻ:
Đã đăng ký tài khoản merchant thành công và có được
app_id
,mac_key
từ Merchant Portal.Hiểu cách sử dụng và đặc điểm kỹ thuật của các API sau:
Khái niệm callback và truyền dữ liệu an toàn.
Cách thực hiện
Luồng thanh toán như sau:
Tích hợp
Phần này tập trung vào cách tích hợp luồng thanh toán tự động từ Zalopay. Một quy trình công việc điển hình bao gồm các bước sau:
- Khởi tạo liên kết với một hợp đồng (agreement).
- Thanh toán bằng Token.
- Hủy liên kết một hợp đồng (agreement).
- Truy vấn thông tin người dùng.
Bước 1. Khởi tạo liên kết với một hợp đồng
Để người dùng có thể thanh toán bằng tính năng thanh toán tự động, trước tiên người bán phải khởi tạo yêu cầu tạo một liên kết mới cho hợp đồng liên kết tài khoản Zalopay của người dùng với ứng dụng của bạn.
Từ máy chủ của bạn, hãy gọi CreateBinding API. Để cụ thể hơn, vui lòng tham khảo API Explorer để biết các tham số quan trọng khác.
Đây là một ví dụ về cách tạo liên kết cho một hợp đồng:
src/api/agreement/bind/index.js// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-tokenized-payment
import axios from "axios";
import CryptoJS from "crypto-js";
import {API_ROUTES, configZLP, ZLP_API_PATH} from "../../config";
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const binding_data = {};
const bind = {
app_id: configZLP.app_id,
app_trans_id: req.body.appTransID,
req_date: Date.now(), // milliseconds
identifier: "ZLP User",
max_amount: 0,
binding_type: 'WALLET',
binding_data: JSON.stringify(binding_data),
callback_url: configZLP.host + API_ROUTES.AGREEMENT_CALLBACK,
redirect_url: "http://localhost:3000/cart" // testing purpose
};
// appid|app_trans_id|appuser|amount|apptime|embeddata|item
const data = configZLP.app_id + "|" + bind.app_trans_id + "|" + bind.binding_data + "|" + bind.binding_type + "|" + bind.identifier + "|" + bind.max_amount + "|" + bind.req_date;
bind.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
axios.post(configZLP.zlp_endpoint + ZLP_API_PATH.AGREEMENT_BINDING, null, {params: bind})
.then(result => {
if (result.data.return_code === 1) {
res.status(200).json({
binding_token: result.data.binding_token,
binding_qr_link: result.data.binding_qr_link // web-based scenario
});
} else {
res.status(200).json({
error: true,
error_code: result.data.sub_return_code,
message: result.data.sub_return_message
});
}
})
.catch(err => console.log(err));
} catch (err) {
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}Sau đó bạn sẽ nhận được phản hồi như thế này:
{
"return_code": 1,
"return_message": "...",
"sub_return_code": 1,
"sub_return_message": "...",
"binding_token": "220428XjNBVZQ7gD2ebs2gVL1E87MBVp",
"deep_link": "zalopay://launch/app/730?view=authorize&b=220428XjNBVZQ7gD2ebs2gVL1E87MBVp",
"binding_qr_link": "https://sbbinding.zalopay.vn?binding_token=220428XjNBVZQ7gD2ebs2gVL1E87MBVp",
"short_link": "https://zlpmcagp.zalopay.vn/oauthbe/agreement/220428XjNBVZQ7gD2ebs2gVL1E87MBVp"
}Hiển thị mã QR
Binding response bao gồm các liên kết mà người bán cần điều hướng để người dùng có thể xác nhận sự liên kết trên nền tảng của người bán:
deep_link
: Liên kết để xác nhận hợp đồng trong ứng dụng di động.binding_qr_link
: URL dành cho người dùng xác nhận hợp đồng trong môi trường web. Zalopay đã tạo mã QR cho nguời bán.short_link
: URL cho hợp đồng người dùng trong môi trường web. Người bán cần tạo QR từ liên kết này.Sau khi xác nhận, Zalopay sẽ chuyển hướng đến trang web bán hàng hoặc ứng dụng bán hàng với các thông số:
Tham số Kiểu dữ liệu Độ dài tối đa Mô tả binding_id String 64 ID của Token liên kết đã được chấp thuận để thanh toán tự động. status Int 1: Thành công, nếu khác là thất bại
Hãy sử dụng
binding_id
này để truy vấn trạng thái liên kết trong trường hợp bạn không nhận được callback liên kết từ Zalopay.Nhận kết quả liên kết
Khi người dùng hoàn tất việc liên kết bằng cách quét mã QR và xác nhận, Zalopay sẽ thông báo kết quả liên kết thông qua callback bằng request POST với
Content-Type: application/json
.Dữ liệu callback bao gồm trường
pay_token
mà người bán sẽ lưu trữ trong hệ thống cho các thanh toán sau này sử dụng token này. Cụ thể hơn:No Tham số Kiểu dữ liệu Độ dài tối đa Mô tả 1 app_id Int app_id
của người bán.2 app_trans_id String 40 Merchant unique ID để liên kết. 3 binding_id String 64 ID của liên kết đã được xác nhận trong hệ thống Zalopay. 4 pay_token String 128 Token công khai sử dụng khi thanh toán tự động. 5 server_time Int Server timestamp (seconds). 6 merchant_user_id String 128 Trường định danh người bán khi liên kết. 7 status Int Trạng thái callback:
1: Người dùng xác nhận hợp đồng
2: Người dùng thay đổi hợp đồng8 msg_type Int 1: Thành công, còn lại là thất bại 9 zp_user_id String 64 Định danh của người dùng Zalopay cho mỗi app_id của người bán. 10 masked_user_phone String 20 Số phone đã được che (Ex: masked_user_phone: "****6938"). cẩn thận- Điều quan trọng là phải xác minh rằng yêu cầu thực sự đến từ Zalopay bằng cách sử dụng callback key để xác thực dữ liệu của lệnh callback.
- Vì lý do bảo mật, callback url của bạn phải có cùng domain với máy chủ của bạn.
- Callback end-point của bạn phải được truy cập công khai.
- Do sự cố kỹ thuật như mạng hết thời gian chờ, service của bạn không khả dụng để phục vụ yêu cầu gọi lại... Zalopay có thể không thông báo được cho bạn khi callback.
Dưới đây là một số ví dụ:
src/api/callback/agreement/index.js// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-tokenized-payment
import {configZLP} from "../../config";
import CryptoJS from "crypto-js";
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
let result = {};
try {
let dataStr = req.body.data;
let reqMac = req.body.mac;
let dataJson = JSON.parse(dataStr, configZLP.key2);
let status = dataJson["status"];
let mac = CryptoJS.HmacSHA256(dataStr, configZLP.key2).toString();
// kiểm tra callback hợp lệ (đến từ Zalopay server)
if (reqMac !== mac) {
// callback không hợp lệ
result.return_code = -1;
result.return_message = "mac not equal";
} else {
if (status === 1) { // Confirmed
console.log("Confirmed Binding callback received!");
console.log("Please provide mechanism to store payToken=", dataJson["pay_token"]);
} else if (status === 3) { // Cancelled
console.log(`Cancelled Binding callback received!`);
} else if (status === 4) { // Disabled
console.log("Disabled Binding callback received!");
console.log("Please provide mechanism to clear agreement info");
}
result.return_code = 1;
result.return_message = "success";
}
} catch (ex) {
result.return_code = 0; // Zalopay server sẽ callback lại (tối đa 3 lần)
result.return_message = ex.message;
}
// thông báo kết quả cho Zalopay server
res.json(result);
} catch (err) {
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}Chúng tôi khuyên bạn nên chủ động truy vấn trạng thái liên kết. Điều này có thể được thực hiện bằng cách gọi
QueryPaymentToken API
.Code mẫu của chúng tôi để truy vấn liên kết bạn vừa tạo:
src/api/agreement/query/index.jsimport axios from "axios";
import CryptoJS from "crypto-js";
import qs from "qs";
import {configZLP, ZLP_API_PATH} from "../../config";
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
let postData = {
app_id: configZLP.app_id,
app_trans_id: req.body.appTransID,
req_date: Date.now(), // milliseconds
}
let data = postData.app_id + "|" + postData.app_trans_id + "|" + postData.req_date; // app_id|app_trans_id|req_date
postData.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
let postConfig = {
method: 'post',
url: configZLP.zlp_endpoint + ZLP_API_PATH.AGREEMENT_QUERY,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: qs.stringify(postData)
};
axios(postConfig)
.then(function (response) {
res.status(200).json(response.data);
})
.catch(function (error) {
console.log(error);
});
} catch (err) {
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}Một liên kết sẽ hết hạn và được đánh dấu là KHÔNG HỢP LỆ sau 15 phút nếu người dùng không xác nhận hoặc cập nhật.
- Bạn có thể ngừng truy vấn trạng thái liên kết sau khoảng thời gian này và coi trạng thái mới nhất là trạng thái cuối cùng.
- Nếu bạn không nhận được bất kỳ callback nào, bạn có thể đánh dấu việc liên kết này là không thành công.
Bước 2. Thanh toán bằng Token
Sau khi liên kết được thiết lập thành công và người dùng của bạn đã sẵn sàng thanh toán, trước tiên hãy kiểm tra xem người dùng có thể thanh toán đơn đặt hàng hay không bằng cách gọi
QueryBalance API
.Dưới đây là ví dụ về cách bạn sẽ truy vấn số dư của một người dùng cụ thể trước khi thanh toán:
src/api/agreement/balance/index.js// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-tokenized-payment
import axios from "axios";
import CryptoJS from "crypto-js";
import qs from "qs";
import {configZLP, ZLP_API_PATH} from "../../config";
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
let postData = {
app_id: configZLP.app_id,
identifier: "ZLP User",
pay_token: req.body.payToken,
amount: req.body.amount,
req_date: Date.now(), // milliseconds
}
let data = postData.app_id + "|" + postData.pay_token + "|" + postData.identifier + "|" + postData.amount + "|" + postData.req_date; //app_id|pay_token|identifier|amount|req_date
postData.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
let postConfig = {
method: 'post',
url: configZLP.zlp_endpoint + ZLP_API_PATH.AGREEMENT_BALANCE,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: qs.stringify(postData)
};
axios(postConfig)
.then(function (response) {
res.status(200).json(response.data);
})
.catch(function (error) {
console.log(error);
});
} catch (err) {
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}Phản hồi bao gồm một mảng đối tượng chi tiết số dư chứa trường
payable
thể hiện liệu người dùng có thể thực hiện thanh toán trên kênh thanh toán hay không.{
"return_code": 1,
"return_message": "...",
"sub_return_code": 1,
"sub_return_message": "...",
"data": [
{
"channel": 38,
"payable": true,
"bank_code": ""
}
],
"discount_amount": 0
}Bạn cần làm theo bên dưới cho bước tiếp theo của mình:
Return code Payable Mô tả 1 true Người dùng có thể thanh toán -> Gọi Pay by token API 1 false Người dùng không thể thanh toán -> Không gọi Pay by token API 2 Người dùng không thể thanh toán -> Không gọi Pay by token API Nếu người dùng có thể thanh toán, hãy gọi
CreateOrder API
để tạo đơn đặt hàng thanh toán theo hợp đồng.Dưới đây là một số ví dụ về cách bạn sẽ xử lý nó dưới backend:
/src/api/create/index.js// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-dynamic-qrcode
import axios from "axios";
import CryptoJS from "crypto-js";
import moment from "moment";
import { configZLP } from "../config";
const embed_data = { zlppaymentid: "P271021" };
const items = [{}]; // todo: collect items from Cart page
const transID = Math.floor(Math.random() * 1000000);
const appTransID = `${moment().format('YYMMDD')}_${transID}`;
const order = {
app_id: configZLP.app_id,
app_trans_id: appTransID,
app_user: "user123",
app_time: Date.now(), // miliseconds
item: JSON.stringify(items),
embed_data: JSON.stringify(embed_data),
amount: 50000,
description: `Payment for the order #${transID}`,
bank_code: "zalopayapp",
callback_url: configZLP.callback_url
};
const data = [configZLP.app_id, order.app_trans_id, order.app_user, order.amount, order.app_time, order.embed_data, order.item].join("|");
order.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
axios.post(configZLP.endpoint + 'create', null, { params: order })
.then(result => {
res.status(200).json({
appTransID: appTransID,
url: result.data.order_url
});
})
.catch(err => console.log(err));Sau khi Zalopay chấp nhận lệnh thanh toán của bạn, bạn sẽ nhận được phản hồi như sau:
{
"return_code": 1,
"return_message": "...",
"sub_return_code": 1,
"sub_return_message": "...",
"zp_trans_token": "20090400000112548Y3z18",
"order_url": "https://sbgateway.zalopay.vn/openinapp?order=eyJ6cHRyYW5zdG9rZW4iOiIyMDA5MDQwMDAwMDExMjU0OFkzejE4IiwiYXBwaWQiOjI1NTN9",
"order_token": "ptazBLb128DJ6MSe4d-I2okWQpO7FdUwK9VA4MNqFliUIO1SM3E8ElOsum-iie61rou4A1lblWwddvCwKObzFpo0xJRX4AgP6moSsVqKsxM8K-Duix5ZdH3HFNN07fk7"
}Nếu bạn nhận được phản hồi với
return_code
khác 1, hãy tham khảo tài liệu tham khảo về mã trạng thái để khắc phục sự cố.Sau khi người bán tạo đơn hàng thanh toán thành công, bạn cần gọi
PayByToken API
để thông báo cho Zalopay tiến hành thanh toán.Đây là một ví dụ về cách bạn tạo liên kết cho một hợp đồng:
/src/api/agreement/pay/index.js// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-dynamic-qrcode
import axios from "axios";
import CryptoJS from "crypto-js";
import {configZLP, ZLP_API_PATH} from "../../config";
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const postData = {
app_id: configZLP.app_id,
identifier: "ZLP User",
pay_token: req.body.payToken,
zp_trans_token: req.body.zpTransToken,
req_date: Date.now(), // milliseconds
};
// appid|app_trans_id|appuser|amount|apptime|embeddata|item
const data = configZLP.app_id + "|" + postData.identifier + "|" + postData.zp_trans_token + "|" + postData.pay_token + "|" + postData.req_date;
postData.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
axios.post(configZLP.zlp_endpoint + ZLP_API_PATH.AGREEMENT_PAY, null, {params: postData})
.then(result => {
if (result.data.return_code === 1) {
res.status(200).json(result.data);
} else {
res.status(200).json({
error: true,
error_code: result.data.sub_return_code,
message: result.data.sub_return_message
});
}
})
.catch(err => console.log(err));
} catch (err) {
console.log(err)
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}Callback đơn hàng thanh toán
Sau khi thanh toán đã được xử lý thành công, một callback sẽ được gửi đến người bán để biết chi tiết về khoản thanh toán.
Logic xử lý giống như Dynamic QR, bạn có thể tham khảo tại đây.
Buớc 3. Hủy liên kết một hợp đồng
Trong trường hợp người dùng của bạn không muốn sử dụng tính năng thanh toán tự động, hãy gọi Unbind API
với binding_id
được trả về khi liên kết được xác nhận với Zalopay.
Phản hồi mẫu:
{
"return_code": 1,
"return_message": "...",
"sub_return_code": 1,
"sub_return_message": "..."
}
Zalopay cũng cho phép hủy liên kết hợp đồng từ Zalopay App. Trong trường hợp này, nhà bán hàng sẽ nhận được thông báo hủy liên kết được gửi từ Zalopay thông qua callback_url
được gửi trong yêu cầu liên kết.
Bước 4. Truy vấn thông tin người dùng
Sau khi khách hàng xác nhận hợp đồng, bạn có thể sử dụng pay_token
trả về từ Zalopay callback để truy vấn thông tin cơ bản của người dùng.
Từ máy chủ của bạn, hãy gọi QueryUser API
.
Hiện tại, API này chỉ trả về số điện thoại đã được che của người dùng.
Mẫu phản hồi:
{
"phone": "****2606",
"return_code": 1,
"return_message": "...",
"sub_return_code": 1,
"sub_return_message": "..."
}
Xem thêm
Tiếp theo
- Đã cài đặt xong? Hãy kiểm thử kết quả tích hợp.