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

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).

Payment flow

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ẻ:

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:

  1. Khởi tạo liên kết với một hợp đồng (agreement).
  2. Thanh toán bằng Token.
  3. Hủy liên kết một hợp đồng (agreement).
  4. 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.

  1. 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"
    }
  2. 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 đaMô tả
      binding_idString64ID của Token liên kết đã được chấp thuận để thanh toán tự động.
      statusInt1: 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.

  3. 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:

    NoTham sốKiểu dữ liệuĐộ dài tối đaMô tả
    1app_idIntapp_id của người bán.
    2app_trans_idString40Merchant unique ID để liên kết.
    3binding_idString64ID của liên kết đã được xác nhận trong hệ thống Zalopay.
    4pay_tokenString128Token công khai sử dụng khi thanh toán tự động.
    5server_timeIntServer timestamp (seconds).
    6merchant_user_idString128Trường định danh người bán khi liên kết.
    7statusIntTrạ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 đồng
    8msg_typeInt1: Thành công, còn lại là thất bại
    9zp_user_idString64Định danh của người dùng Zalopay cho mỗi app_id của người bán.
    10masked_user_phoneString20Số 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.js
    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,
    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

  1. 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 codePayableMô tả
    1trueNgười dùng có thể thanh toán -> Gọi Pay by token API
    1falseNgười dùng không thể thanh toán -> Không gọi Pay by token API
    2Người dùng không thể thanh toán -> Không gọi Pay by token API
  2. 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ố.

  3. 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');
    }
    }
  4. 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": "..."
}
thông tin

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.

thông tin

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.