Zalopay On Delivery
Overview
Zalopay On Delivery (ZOD) is a payment solution that enables users to pay for their goods on delivery by scanning QR codes using Zalopay.
In the next sections, we guide you step by step to integrate Zalopay. You can try out our live demo application to get a quick overview. A complete example implementation (with NextJs) is in the Github repository.
Prerequisites
Before you begin, make sure the following works done for a smooth integration:
Registered merchant account successfully and obtained
app_id
,mac_key
from Merchant Portal.Understood the usage and specification of following APIs:
The concept of callback and secure data transmission.
How it works
The payment flow is as below:
Integration
This section focuses on integrating the ZOD flow from Zalopay. A typical workflow that the merchant needs to handle includes the following steps:
- Initiate ZOD order
- Process payment result
Step 1. Initiate ZOD order
On the merchant's payment page, after customers choose to pay on delivery with Zalopay, the merchant needs to call CreateZODInvoice API
to create a ZOD order.
For more specifying, please refer to the API Explorer for other critical parameters.
Here's an example of how you create a ZOD order:
// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-zod
[...]
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const mcExtInfo = {};
const zodInvoice = {
appId: configZLP.app_id,
mcRefId: req.body.mcRefId,
hubId: "",
driverId: "",
amount: req.body.amount,
receiver: req.body.receiver,
orderInfo: req.body.orderInfo,
mcExtInfo: JSON.stringify(mcExtInfo)
};
// appId|mcRefId|amount|mcExtInfo
const data = zodInvoice.appId + "|" + zodInvoice.mcRefId + "|" + zodInvoice.amount + "|" + zodInvoice.mcExtInfo;
zodInvoice.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
const config = {
method: 'post',
url: configZLP.zlp_endpoint + ZLP_API_PATH.ZOD_CREATE_INVOICE,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(zodInvoice)
};
axios.request(config)
.then(result => {
res.status(200).json({
orderUrl: result.data.orderUrl,
});
})
.catch(err => {
res.status(err.response.status).json({
error: true,
message: err.response.data.message
})
});
} catch (err) {
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
After you would receive a response like this:
{
"orderUrl": "https://sbqrpay.zalopay.vn/zod/{token}"
}
This url is used to generate QR code will be displayed on shipper app and will be expired and mark as INVALID after 1 month since its creation.
Besides, merchants also can retrieve this orderUrl
using /v2/zod/invoice
API.
The merchant will be responsible for encoding the orderUrl
into a QR code. This can be achieved using any QR Code generator library that you find convenient or comfortable to work with.
Here is how Zalopay suggests you generate the QR code:
// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-zod
import { QRCode } from 'antd';
<div id="qr-code">
<QRCode value={result.url}/>
</div>
Step 2. Process payment result
When your buyer completes the payment by scanning the QR code and confirming the payment, Zalopay will notify the payment result via callback with a POST request with Content-Type: application/json
.
The callback data including the transaction information is specified as follows:
No | Params | Data type | Description |
---|---|---|---|
1 | appId | String | The app identifier is provided by Zalopay. |
2 | mcRefId | String | The merchant order's reference ID. |
3 | amount | Int | Amount of order. |
4 | zpTransId | Int | The Zalopay transaction id. |
5 | serverTime | Int | Server timestamp in seconds. |
6 | channel | Int | Payment channel. |
7 | zpUserId | String | The identifier of Zalopay user per merchant. |
8 | userFeeAmount | Int | Fee amount. |
9 | discountAmount | Int | Discount amount. |
10 | userChargeAmount | Int | Amount charged from user. |
- It's important to verify that the request actually came from Zalopay by using the callback key to validate the callback's data.
- For security reasons, your callback url must have the same domain as your server.
- Your callback endpoint must be publicly accessible.
- Due to technical issues such as network timeout, your service is unavailable to serve the callback request... Zalopay may not be able to notify you via the callback.
Here are some examples of how you can do it:
// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-zod
[...]
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);
// validate valid callback from Zalopay server
if (reqMac !== mac) {
// invalid callback
result.return_code = -1;
result.return_message = "mac not equal";
} else {
// payment successfully
// merchant update the order's status
let dataJson = JSON.parse(dataStr, configZLP.key2);
console.log(`💰 Payment callback received!`);
console.log("✅ Update order's status = success where mcRefId =", dataJson["mcRefId"]);
result.return_code = 1;
result.return_message = "success";
}
} catch (ex) {
result.return_code = 0; // Zalopay server will send callback again (max is 3)
result.return_message = ex.message;
}
// response to 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');
}
}
We strongly recommend that you actively query the order status. This can be done by calling a POST QueryZODOrder API
.
Our sample code to query the order's status:
// For a working example, please navigate to:
// https://github.com/zalopay-samples/quickstart-nextjs-zod
[...]
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
let params = {
appId: configZLP.app_id,
mcRefId: req.body.mcRefId,
}
let data = params.appId + "|" + params.mcRefId; // appId|mcRefId
params.mac = CryptoJS.HmacSHA256(data, configZLP.key1).toString();
let config = {
method: 'get',
url: configZLP.zlp_endpoint + ZLP_API_PATH.ZOD_QUERY_STATUS,
headers: {
'Content-Type': 'application/json'
},
params
};
axios(config)
.then(function (response) {
res.status(200).json(response.data);
})
.catch(function (error) {
console.log(error.response.data);
});
} catch (err) {
res.status(500).json({statusCode: 500, message: err.message});
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
As mentioned earlier, an order that has been created will automatically expire and be marked as INVALID after 1 month from its creation.
- You can set an interval for querying ZOD order status based on this time box and treat the latest status as its final status.
After getting the final order status from Zalopay, now you can present to your buyer the latest payment status.
See also
What's next
- Finish coding? Verify your integration with testing.