const express = require('express');
const router = express.Router();
const qs = require('qs');
const crypto = require('crypto');
const moment = require('moment');
const db = require('../config/db');
const { denyAdmin } = require('../middleware/deny_admin');
// Cấu hình VNPAY
const tmnCode = process.env.VNP_TMNCODE;
const secretKey = process.env.VNP_HASH_SECRET;
const vnpUrl = process.env.VNP_URL;
const returnUrl = process.env.VNP_RETURN_URL;
router.post('/orders/with-payment-url', denyAdmin, async (req, res) => {
const {
user_id,
address,
phone,
items,
totalAmount,
subtotalAmount,
shippingFee,
couponDiscount
} = req.body;
try {
console.log('➡️ Bắt đầu tạo đơn hàng và lấy URL thanh toán cho user:', user_id);
// --- 1. Tạo đơn hàng trong cơ sở dữ liệu ---
const [orderResult] = await db.query(
'INSERT INTO orders (user_id, address, phone, total_amount, subtotal_amount, shipping_fee, coupon_discount, status, payment_status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, "pending", "pending", NOW())',
[user_id, address, phone, totalAmount, subtotalAmount, shippingFee, couponDiscount]
);
const orderId = orderResult.insertId;
console.log('🧾 Đã tạo đơn hàng, ID:', orderId);
// --- 2. Thêm sản phẩm vào bảng order_items ---
for (const item of items) {
await db.query(
'INSERT INTO order_items (order_id, product_id, quantity, price, subtotal) VALUES (?, ?, ?, ?, ?)',
[orderId, item.product_id, item.quantity, item.price, item.price * item.quantity]
);
// Giảm số lượng trong kho
await db.query(
'UPDATE products SET stock = stock - ? WHERE id = ?',
[item.quantity, item.product_id]
);
console.log(`🔻 Đã trừ ${item.quantity} sản phẩm ID ${item.product_id} khỏi kho`);
}
// 3. Tạo dữ liệu VNPAY
const createDate = moment().format('YYYYMMDDHHmmss');
const amount = Math.round(totalAmount);
// Chuẩn IP (loại bỏ ::ffff nếu có)
// let ipAddr = req.headers['x-forwarded-for'] || req.socket.remoteAddress || '127.0.0.1';
// ipAddr = ipAddr.includes('::ffff:') ? ipAddr.replace('::ffff:', '') : ipAddr;
let ipAddr = '127.0.0.1';
let vnp_Params = {
vnp_Version: '2.1.0',
vnp_Command: 'pay',
vnp_TmnCode: tmnCode,
vnp_Locale: 'vn',
vnp_CurrCode: 'VND',
vnp_TxnRef: orderId,
vnp_OrderInfo: `Thanh toan don hang ${orderId}`,
vnp_OrderType: 'other',
vnp_Amount: amount * 100,
vnp_ReturnUrl: returnUrl,
vnp_IpAddr: ipAddr,
vnp_CreateDate: createDate,
vnp_BankCode: 'NCB',
};
// 4. Ký và tạo link
const sortedParams = sortObject(vnp_Params);
const signData = qs.stringify(sortedParams, { encode: false }); // ✅ KHÔNG ENCODE
const hmac = crypto.createHmac('sha512', secretKey);
const signed = hmac.update(signData).digest('hex');
console.log('🔍 Chuỗi để ký:', signData);
// Thêm chữ ký vào object
sortedParams['vnp_SecureHash'] = signed;
// Tạo URL
const paymentUrl = `${vnpUrl}?${qs.stringify(sortedParams, { encode: false })}`;
console.log(`➡️ URL thanh toán cho orderId ${orderId}: ${paymentUrl}`);
// ✅ Trả về cho frontend
res.status(200).json({
orderId,
paymentUrl
});
} catch (err) {
console.error('❌ Lỗi khi tạo đơn hàng hoặc lấy URL thanh toán:', err);
res.status(500).json({
msg: 'Lỗi khi tạo đơn hàng hoặc lấy URL thanh toán'
});
}
});
//cái vnpay cũ chưa tích hợp giá khuyến mãi và phí ship
// router.post('/orders/with-payment-url',denyAdmin, async (req, res) => {
// const { user_id, total_amount, address, phone, items } = req.body;
// try {
// // 1. Tạo đơn hàng
// const [result] = await db.query(
// `INSERT INTO orders (user_id, total_amount, address, phone, payment_status, status, revenue_tracked, created_at)
// VALUES (?, ?, ?, ?, 'pending', 'pending', 0, NOW())`,
// [user_id, total_amount, address, phone]
// );
// const orderId = result.insertId;
// console.log(`✅ Đã tạo đơn hàng vnpay ID: ${orderId}`);
// // 2.1. Chèn vào bảng order_items
// if (Array.isArray(items)) {
// for (const item of items) {
// console.log(`➕ Thêm sản phẩm vào đơn hàng:`, item);
// // 1. Thêm vào order_items
// await db.query(
// 'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)',
// [orderId, item.product_id, item.quantity, item.price]
// );
// // 2. Trừ kho hàng
// await db.query(
// 'UPDATE products SET stock = stock - ? WHERE id = ?',
// [item.quantity, item.product_id]
// );
// console.log(`🔻 Đã trừ ${item.quantity} sản phẩm ID ${item.product_id} khỏi kho`);
// // 3.Xóa giỏ hàng sau khi đặt hàng thành công
// await db.query('DELETE FROM carts WHERE user_id = ?', [user_id]);
// console.log('🧹 Đã xóa giỏ hàng sau khi đặt hàng');
// }
// } else {
// console.warn('⚠️ Không có danh sách sản phẩm để lưu vào order_items');
// }
// // 2. Lấy lại đơn hàng
// const [[order]] = await db.query('SELECT * FROM orders WHERE id = ?', [orderId]);
// // 3. Tạo dữ liệu VNPAY
// const createDate = moment().format('YYYYMMDDHHmmss');
// // const orderIdVNPay = moment().format('DDHHmmss');
// const orderIdVNPay = `${order.id}`;
// const amount = order.total_amount;
// // Chuẩn IP (loại bỏ ::ffff nếu có)
// // let ipAddr = req.headers['x-forwarded-for'] || req.socket.remoteAddress || '127.0.0.1';
// // ipAddr = ipAddr.includes('::ffff:') ? ipAddr.replace('::ffff:', '') : ipAddr;
// let ipAddr = '127.0.0.1';
// let vnp_Params = {
// vnp_Version: '2.1.0',
// vnp_Command: 'pay',
// vnp_TmnCode: tmnCode,
// vnp_Locale: 'vn',
// vnp_CurrCode: 'VND',
// vnp_TxnRef: orderIdVNPay,
// vnp_OrderInfo: `Thanh toan don hang ${order.id}`,
// vnp_OrderType: 'other',
// vnp_Amount: amount * 100,
// vnp_ReturnUrl: returnUrl,
// vnp_IpAddr: ipAddr,
// vnp_CreateDate: createDate,
// vnp_BankCode: 'NCB',
// };
// // 4. Ký và tạo link
// const sortedParams = sortObject(vnp_Params);
// const signData = qs.stringify(sortedParams, { encode: false }); // ✅ KHÔNG ENCODE
// const hmac = crypto.createHmac('sha512', secretKey);
// const signed = hmac.update(signData).digest('hex');
// console.log('🔍 Chuỗi để ký:', signData);
// // Thêm chữ ký vào object
// sortedParams['vnp_SecureHash'] = signed;
// // Tạo URL
// const paymentUrl = `${vnpUrl}?${qs.stringify(sortedParams, { encode: false })}`;
// console.log(`➡️ URL thanh toán cho orderId ${order.id}: ${paymentUrl}`);
// res.json({
// message: 'Order created and payment URL generated',
// orderId: order.id,
// paymentUrl: paymentUrl,
// });
// } catch (error) {
// console.error('❌ Lỗi tạo đơn hàng hoặc tạo URL VNPAY:', error);
// res.status(500).json({ message: 'Lỗi hệ thống khi tạo đơn hàng + thanh toán' });
// }
// });
router.get('/vnpay_return', async (req, res) => {
try {
let vnp_Params = req.query;
const secureHash = vnp_Params['vnp_SecureHash'];
delete vnp_Params['vnp_SecureHash'];
delete vnp_Params['vnp_SecureHashType'];
// ✅ Kiểm tra chữ ký
const sortedParams = sortObject(vnp_Params);
const signData = qs.stringify(sortedParams, { encode: false });
const hmac = crypto.createHmac('sha512', secretKey);
const signed = hmac.update(signData).digest('hex');
if (secureHash !== signed) {
return res.status(400).send('<h2 style="color:red;">❌ Sai chữ ký (Invalid Checksum)</h2>');
}
const responseCode = vnp_Params['vnp_ResponseCode'];
const txnRef = Number(vnp_Params['vnp_TxnRef']); // chính là order_id
const transactionNo = vnp_Params['vnp_TransactionNo'];
if (responseCode !== '00') {
return res.send(`
<h2 style="color:red;">❌ Giao dịch thất bại</h2>
<p>Mã lỗi: ${responseCode}</p>
<a href="/">Quay lại</a>
`);
}
// ✅ Trả về giao diện xác nhận thành công
return res.send(`
<h2 style="color:green;">✅ Thanh toán thành công!</h2>
<p>Đơn hàng: ${txnRef}</p>
<p>Mã giao dịch: ${transactionNo}</p>
<a href="/">Quay lại trang chủ</a>
`);
} catch (error) {
console.error('❌ Lỗi xử lý callback VNPAY:', error);
return res.status(500).send('<h2 style="color:red;">❌ Lỗi hệ thống!</h2>');
}
});
function sortObject(obj) {
let sorted = {};
let str = [];
let key;
for (key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
str.push(encodeURIComponent(key));
}
}
str.sort();
for (key = 0; key < str.length; key++) {
const decodedKey = decodeURIComponent(str[key]);
sorted[str[key]] = encodeURIComponent(obj[decodedKey]).replace(/%20/g, "+");
// sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, "+");
}
return sorted;
}
module.exports = router;
P/s: Code mẫu vnpay expressJs