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

Thanh toán QR Động

Tổng quan

Thanh toán QR Động là một cách tích hợp thanh toán mạnh mẽ nhưng việc tích hợp sẽ hơi phức tạp. Cách tích hợp này phù hợp với những người bán có khả năng kỹ thuật để nhúng Zalopay vào các ứng dụng hiện có của họ.

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 trong 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 hoạt động

Luồng thanh toán như sau:

  1. Bạn thêm một nút thanh toán vào ứng dụng web của mình.
  2. Người mua của bạn nhấp vào nút thanh toán.
  3. Backend của bạn gọi CreateOrder API để tạo một lệnh thanh toán mới.
  4. Giao diện người dùng của bạn hiển thị mã QR cho người mua dựa trên phản hồi được trả về.
  5. Người mua hoàn tất thanh toán bằng cách quét mã QR bằng ứng dụng Zalopay và thực hiện thanh toán.
  6. Backend của bạn nhận thông báo kết quả thanh toán.
  7. Bạn hiển thị thông báo kết quả thanh toán cho người mua.

Cụ thể, luồng thanh toán sẽ như sau:



Tích hợp

Phần này tập trung vào cách tích hợp luồng mã QR động từ Zalopay. Một quy trình điển hình bao gồm các bước sau:

  1. Thêm nút thanh toán.
  2. Tạo đơn hàng.
  3. Xuất mã QR.
  4. Hiển thị xác nhận.

Bước 1. Thêm nút thanh toán

Trong trang Giỏ hàng, bạn có thể thêm một nút để điều hướng đến trang Thanh toán như sau:

pages/cart/index.js (Cart page)
// For a  working example, please navigate to: 
// https://github.com/zalopay-samples/quickstart-nextjs-dynamic-qrcode

import Head from 'next/head';
import {useRouter} from "next/router";

const Cart = () => {
const [redirecting, setRedirecting] = useState(false);
const router = useRouter();

const redirectToCheckout = async (e) => {
e.preventDefault();
await router.push('/checkout');
};

return (
<>
<Head>
<title>My Shopping Cart </title>
</Head>
{/* ... */}
<button onClick={redirectToCheckout}>
{redirecting ? 'Redirecting...': 'Go to Checkout'}
</button>
{/* ... */}
</>
)
}
export default Cart;

Ở trang Thanh toán, để cho ngắn gọn trong ví dụ này, bạn có thể tự động gọi /api/create_order sau khi trang được hiển thị.

pages/checkout/index.js (Checkout page)
import React, {useEffect, useState} from "react";
import axios from "axios";

const Checkout = () => {
// ...
useEffect(async () => {
// create ZLP order
const res = await axios.post('/api/create_order');
setQrCode(res.data.url);
setAppTransId(res.data.appTransID);
}, [])
// ...
}

Bước 2. Tạo đơn hàng

Sau khi đã thu thập đầy đủ thông tin thanh toán và chuẩn bị thanh toán, bạn cần gửi lệnh thanh toán của mình đến máy chủ Zalopay bằng CreateOrder API.

Dưới đây là một số ví dụ về cách bạn sẽ xử lý /api/create_order dưới backend:

/src/api/create_order/index.js (Backend API)
// 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ố.

Bước 3. Xuất mã QR

Sau khi Zalopay hoàn thành việc tạo lệnh thanh toán, bạn hãy hiển thị chi tiết thanh toán cho người mua để họ hoàn tất thanh toán. Bạn sẽ cần chuyển order_url sang mã QR, điều này có thể được thực hiện bởi bất kỳ thư viện tạo Mã QR nào mà bạn cảm thấy thoải mái.

thông tin

Vui lòng đảm bảo rằng bạn tuân theo hướng dẫn của Zalopay khi bạn trình bày nhãn hiệu của chúng tôi.

Sau đây là cách Zalopay gợi ý bạn tạo mã QR:

pages/checkout/index.js (CheckoutPage)
// For a  working example, please navigate to: 
// https://github.com/zalopay-samples/quickstart-nextjs-dynamic-qrcode

import { QRCode } from 'antd';

<div id="qr-code">
<QRCode value={result.url}/>
</div>

Buớc 4. Hiển thị xác nhận

Khi người mua hoàn tất thanh toán bằng cách quét mã QR và xác nhận thanh toán, Zalopay sẽ thông báo hoàn tất thanh toán thông qua callback bằng method POST với Content-Type:application/json.

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ó thể truy cập công khai.
  • Do sự cố kỹ thuật như mạng hết thời gian chờ, dịch vụ của bạn không khả dụng để phục vụ yêu cầu callback... Zalopay có thể không thông báo được cho bạn thông qua callback.

Dưới đây là một số ví dụ:

api/callback/index.js
// For a  working example, please navigate to: 
// https://github.com/zalopay-samples/quickstart-nextjs-dynamic-qrcode

import CryptoJS from "crypto-js";
import { configZLP } from "../config";

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 mac = CryptoJS.HmacSHA256(dataStr, configZLP.key2).toString();
console.log("mac =", mac);

if (reqMac !== mac) {
result.return_code = -1;
result.return_message = "mac not equal";
} else {
let dataJson = JSON.parse(dataStr, configZLP.key2);
console.log(`💰 Payment Callback received!`);
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;
result.return_message = ex.message;
}

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 thanh toán bằng QueryOrder API.

Code mẫu để truy vấn đơn hàng bạn có thể tham khảo:

api/query_status/index.js
import axios from "axios";
import CryptoJS from "crypto-js";
import qs from "qs";
import { configZLP } from "../config";

let postData = {
app_id: configZLP.app_id,
app_trans_id: appTransId, // Input your app_trans_id
}

let data = [postData.app_id, postData.app_trans_id, configZLP.key1].join("|"); // appid|app_trans_id|key1
postData.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();

let postConfig = {
method: 'post',
url: configZLP.endpoint + 'query',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: qs.stringify(postData)
};

const result = await axios(postConfig).then(response => {
return response.data
})
if (returnCode === 1) {
console.log(`💰 Payment received!`);
console.log("✅ Update order's status = success where app_trans_id =", postData["app_trans_id"]);
}

Một đơn đặt hàng sẽ hết hạn và được đánh dấu là KHÔNG HỢP LỆ sau 15 phút kể từ khi được tạo.

  • Bạn có thể ngừng truy vấn trạng thái đơn đặt hàng 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 thanh toán này là không thành công.

Sau khi nhận được trạng thái đơn hàng cuối cùng từ Zalopay, bây giờ bạn có thể hiển thị cho người mua trạng thái thanh toán mới nhất.

Xem thêm

Tiếp theo

  • Đã cài đặt xong? Hãy kiểm thử kết quả tích hợp.