Skip to main content

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.

Payment flow

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:

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:

  1. Initiate ZOD order
  2. 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:

src/api/zod/create/index.js
// 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}"
}
info

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:

pages/index.js
// 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:

NoParamsData typeDescription
1appIdStringThe app identifier is provided by Zalopay.
2mcRefIdStringThe merchant order's reference ID.
3amountIntAmount of order.
4zpTransIdIntThe Zalopay transaction id.
5serverTimeIntServer timestamp in seconds.
6channelIntPayment channel.
7zpUserIdStringThe identifier of Zalopay user per merchant.
8userFeeAmountIntFee amount.
9discountAmountIntDiscount amount.
10userChargeAmountIntAmount charged from user.
caution
  • 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:

src/api/callback/index.js
// 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:

src/api/zod/query_status/index.js
// 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.