Tích hợp xác nhận thanh toán
Thực hành lập trình xử lý sự kiện webhook để xác nhận thanh toán
Last updated
require('dotenv').config({ path: '.env' });
let app = require('./app');
async function main() {
app
console.log(`Server on port ${process.env.PORT || 4300}`);
};
main();let express = require("express");
var cors = require('cors');
let app = express();
// Tạo cors
var corsOption = {
origin: true,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
exposedHeaders: ['x-auth-token']
};
app.use(cors(corsOption));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/', require('./routes'));
// Endpoint not found
app.use(function (req, res, next) {
res.status(404).json({
code: 404,
message: 'Endpoint not found'
});
})
// Xử lí khi lỗi ở phía server
app.use(function (err, req, res, next) {
res.status(500).json({
code: 500, error: 'Something went wrong, please try again!'
})
})
app.listen(process.env.PORT || 4300);
module.exports = app;var express = require('express');
var router = express.Router();
//Router này sẽ là webhook nhận thông tin giao dịch từ casso gọi qua được bảo mật bằng secure_token trong header
router.route('/webhook/handler-bank-transfer')
.post(async (req, res, next) => {
res.status(200).json({message: "Hello world"})
})
// Router này sẽ thực hiện tính năng đồng bộ giao dịch tức thì.
// Ví dụ: Khi người dùng chuyển khoản cho bạn và họ ấn nút tôi đã thanh toán thì nên xử lí gọi qua casso đề đồng bộ giao dịch vừa chuyển khoản
router.route('/users-paid')
.post(async (req, res, next) => {
res.status(200).json({message: "Hello world"})
})
// Route này sẽ thực hiện đăng kí webhook dựa vào API KEY và lấy thông tin về business và banks
router.route('/register-webhook')
.post(async (req, res, next) => {
res.status(200).json({message: "Hello world"})
})
module.exports = router;//utils/api.js
const axios = require("axios");
const queryString = require("query-string");
const axiosClient = axios.create({
baseURL: 'https://oauth.casso.vn/v2',
headers: {
"content-type": "application/json",
"Authorization": `Apikey ${api_key}`,
},
paramsSerializer: (params) => queryString.stringify(params),
});
axiosClient.interceptors.request.use(async (config) => {
return config;
});
axiosClient.interceptors.response.use(
(response) => {
if (response && response.data) return response.data;
return response;
},
(error) => {
throw error;
}
);
module.exports = axiosClient;/*utils/get_user_info.util.js*/
getDetailUser: async () => {
let res = await api.get(`/userInfo`);
return res;
},/*utils/sync.util.js*/
syncTransaction: async (bankNumber, apiKey) => {
let res = await api.post('/sync', { bank_acc_id: bankNumber });
return res;
}/*webhook.util.js*/
create: async (data) => {
let res = await api.post('/webhooks', data);
return res;
},
getDetailWebhookById: async (webhookId) => {
let res = await api.get(`/webhooks/${webhookId}`);
return res;
},
updateWebhookById: async (webhookId, data) => {
let res = await api.put(`/webhooks/${webhookId}`, data);
return res;
},
deleteWebhookById: async (webhookId) => {
let res = await api.delete(`/webhooks/${webhookId}`);
return res;
},
deleteWebhookByUrl: async (urlWebhook) => {
// Thêm url vào query để delete https://oauth.casso.vn/v1/webhooks?webhook=https://website-cua-ban.com/api/webhook
let query = { params: { webhook: urlWebhook } };
let res = await api.delete(`/webhooks`, query);
return res;
},/*webhook.util.js*/
parseOrderId: (caseInsensitive, transactionPrefix, description) => {
// Ở đây mình ở sử dụng regex để parse nội dung chuyển khoản có chứa orderId
// CASSO101 => orderId = 101
let re = new RegExp(transactionPrefix);
if (!caseInsensitive)
re = new RegExp(transactionPrefix, 'i');
let matchPrefix = description.match(re);
// Không tồn tại tiền tố giao dịch
if (!matchPrefix) return null;
let orderId = parseInt(description.substring(transactionPrefix.length, description.length));
return orderId;
}//routes/index.js
//Tiền tố giao dịch
const transaction_prefix = 'CASSO';
// Phân biệt chữ hoa/thường trong tiền tố giao dịch
const case_insensitive = false;
//Hạn của đơn hàng là 3 ngày. Quá 3 ngày thì không xử lý
const expiration_date = 3;
// API KEY lấy từ casso
const api_key = '45e40320-e0b7-11eb-a12c-35cc867f21a0';
// secure_token đăng kí khi tạo webhook
const secure_token = 'R5G4cbnN7uSAwfTd'//routes/index.js
router.route('/register-webhook')
.post(async (req, res, next) => {
try {
//Delete Toàn bộ webhook đã đăng kí trước đó với https://ten-mien-cua-ban.com/webhook/handler-bank-transfer
await webhookUtil.deleteWebhookByUrl('https://ten-mien-cua-ban.com/webhook/handler-bank-transfer');
//Tiến hành tạo webhook
let data = {
webhook: 'https://ten-mien-cua-ban.com/webhook/handler-bank-transfer',
secure_token: secure_token,
income_only: true
}
let newWebhook = await webhookUtil.create(data);
// Lấy thông tin về userInfo
let userInfo = await userUtil.getDetailUser();
return res.status(200).json({
code: 200,
message: 'success',
data: {
webhook: newWebhook.data,
userInfo: userInfo.data
}
})
} catch (error) {
next(error)
}
})curl --location --request POST 'http://localhost:4300/register-webhook' \
--header 'Content-Type: application/json'{
"code": 200,
"message": "success",
"data": {
"webhook": {
"id": 415,
"channel": "webhook",
"param1": "https://ten-mien-cua-ban.com/webhook/handler-bank-transfer",
"param2": "R5G4cbnN7uSAwfTd",
"sendOnlyIncome": 1
},
"userInfo": {
"user": {
"id": 1553,
"email": "haonh@magik.vn"
},
"business": {
"id": 1540,
"name": "Hữu Hảo"
},
"bankAccs": [
{
"id": 619,
"bank": {
"bin": 970416,
"codeName": "acb_digi"
},
"bankAccountName": null,
"bankSubAccId": "17271687",
"connectStatus": 1,
"planStatus": 1
},
{
"id": 623,
"bank": {
"bin": 970454,
"codeName": "timoplus"
},
"bankAccountName": null,
"bankSubAccId": "8007041023848",
"connectStatus": 1,
"planStatus": 0
}
]
}
}
}//routes/index.js
router.route('/users-paid')
.post(async (req, res, next) => {
try {
// Để thực hiện tính năng đồng bộ cần có Số tài khoản, Bạn có thể validate bằng schema ở middlewares
// Hoặc có thể kiểm tra trong đây luôn
if (!req.body.accountNumber) {
return res.status(404).json({
code: 404,
message: 'Not foung Account number'
})
}
// Tiến hành gọi hàm đồng bộ qua casso
await syncUtil.syncTransaction(req.body.accountNumber);
return res.status(200).json({
code: 200,
message: 'success',
data: null
})
} catch (error) {
next(error)
}
})//routes/index.js
router.route('/webhook/handler-bank-transfer')
.post(async (req, res, next) => {
try {
// B1: Ở đây mình sẽ thực hiện check secure-token. Bình thường phần này sẽ nằm trong middlewares
// Mình sẽ code trực tiếp tại đây cho dễ hình dung luồng. Nếu không có secure-token hoặc sai đều trả về lỗi
if (!req.header('secure-token') || req.header('secure-token') != secure_token) {
return res.status(401).json({
code: 401,
message: 'Missing secure-token or wrong secure-token'
})
}
// B2: Thực hiện lấy thông tin giao dịch
for (let item of req.body.data) {
// Lấy thông orderId từ nội dung giao dịch
let orderId = webhookUtil.parseOrderId(case_insensitive, transaction_prefix, item.description);
// Nếu không có orderId phù hợp từ nội dung ra next giao dịch tiếp theo
if (!orderId) continue;
// Kiểm tra giao dịch còn hạn hay không? Nếu không qua giao dịch tiếp theo
if ((((new Date()).getTime() - (new Date(item.when)).getTime()) / 86400000) >= expiration_date) continue;
// Bước quan trọng đây.
// Sau khi có orderId Thì thực hiện thay đổi các trang thái giao dịch
// Ví dụ như kiểm tra orderId có tồn tại trong danh sách các đơn hàng của bạn?
// Sau đó cập nhật trạng thái theo orderId và amount nhận được: đủ hay thiếu tiền...
// Và một số chức năng khác có thể tùy biến
}
return res.status(200).json({
code: 200,
message: 'success',
data: null
})
} catch (error) {
next(error)
}
})