Hướng dẫn bảo mật
Các best practices để bảo mật khi tích hợp Payment Gateway PayerScan.
Bảo mật API Key
Không bao giờ để lộ API Key
API Key dùng để xác thực các request đến API PayerScan. Nó không bao giờ được hiển thị cho người dùng cuối.
// ❌ SAI - Gọi API từ frontend (API Key bị lộ cho mọi người)
fetch('https://api.payerscan.com/payment/crypto', {
headers: { 'x-api-key': 'EQnhBYpknGAP...' } // API Key bị lộ!
})
// ✅ ĐÚNG - Gọi API từ backend server
// Frontend gọi API nội bộ của bạn
fetch('/api/create-payment', { body: { amount: 100 } })
// Backend của bạn gọi PayerScan (API Key an toàn ở server)
app.post('/api/create-payment', async (req, res) => {
const result = await fetch('https://api.payerscan.com/payment/crypto', {
headers: { 'x-api-key': process.env.PAYERSCAN_API_KEY }
})
res.json(result)
})
Lưu trữ API Key an toàn
# ✅ Dùng environment variables
PAYERSCAN_API_KEY=EQnhBYpknGAP...
# ❌ Không hardcode trong code
const API_KEY = 'EQnhBYpknGAP...' # KHÔNG LÀM THẾ NÀY
Lưu ý: Không bao giờ commit API Key vào hệ thống quản lý phiên bản (Git). Thêm
.envvào file.gitignore.
Rotate API Key định kỳ
- Đổi API Key nếu nghi ngờ bị lộ
- Vào trang quản lý Store → Generate New API Key
- Cập nhật API Key mới trên server ngay lập tức
- API Key cũ sẽ bị vô hiệu hóa ngay sau khi tạo key mới
Bảo mật Webhook
Sử dụng HTTPS
Luôn dùng HTTPS cho callback_url để mã hóa dữ liệu truyền tải và ngăn chặn tấn công man-in-the-middle.
// ✅ ĐÚNG - Dùng HTTPS
callback_url: 'https://your-server.com/webhook/payment'
// ❌ SAI - Dùng HTTP (dữ liệu không được mã hóa, dễ bị đánh cắp)
callback_url: 'http://your-server.com/webhook/payment'
Xác thực webhook request
Luôn xác thực rằng webhook request thực sự đến từ PayerScan:
app.post('/webhook/payment', express.json(), async (req, res) => {
try {
const { merchant_id, api_key, trans_id, request_id, status, amount, transaction_hash } = req.body
// 1. Xác minh merchant_id (cả completed và expired)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).json({ error: 'Invalid merchant' })
}
// 2. Xác minh api_key (chỉ completed — expired không có api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).json({ error: 'Invalid API Key' })
}
// 3. Tìm đơn hàng theo request_id (nếu có) hoặc trans_id
let order = null
if (request_id) {
order = await db.orders.findOne({ request_id })
}
if (!order) {
order = await db.orders.findOne({ trans_id })
}
if (!order) {
return res.status(404).json({ error: 'Order not found' })
}
// 4. Chỉ xử lý đơn hàng đang ở trạng thái có thể cập nhật (idempotent)
const processableStatuses = ['waiting', 'pending', 'processing']
if (!processableStatuses.includes(order.status)) {
return res.status(200).json({ message: 'Already processed' })
}
// 5. Xác minh amount khớp (chỉ completed — chống giả mạo)
if (status === 'completed') {
if (parseFloat(amount) !== parseFloat(order.amount)) {
return res.status(400).json({ error: 'Amount mismatch' })
}
}
// 6. Kiểm tra transaction_hash chưa được dùng (chống replay)
if (status === 'completed') {
const existingTx = await db.orders.findOne({ transaction_hash })
if (existingTx) {
return res.status(409).json({ error: 'Transaction hash already used' })
}
}
// 7. Atomic update — kèm filter status để chống race condition
if (status === 'completed') {
const result = await db.orders.update(
{ trans_id, status: { $in: processableStatuses } },
{ status: 'completed', transaction_hash }
)
if (result.modifiedCount === 0) {
return res.status(200).json({ message: 'Already processed' })
}
} else if (status === 'expired') {
const result = await db.orders.update(
{ trans_id, status: { $in: processableStatuses } },
{ status: 'expired' }
)
if (result.modifiedCount === 0) {
return res.status(200).json({ message: 'Already processed' })
}
}
res.status(200).json({ success: true })
} catch (error) {
console.error('Webhook processing error:', error)
res.status(500).json({ error: 'Internal server error' })
}
})
Xử lý idempotent
Webhook có thể được gửi lại tối đa 5 lần. Đảm bảo xử lý nhiều lần vẫn an toàn:
// ✅ ĐÚNG - Chỉ cập nhật đơn hàng đang ở trạng thái có thể xử lý
const processableStatuses = ['waiting', 'pending', 'processing']
if (!processableStatuses.includes(order.status)) {
return res.status(200).json({ message: 'Already processed' })
}
// ❌ SAI - Không kiểm tra, có thể cộng tiền nhiều lần
await addBalance(user, amount) // Nếu webhook gửi 2 lần → cộng 2 lần!
Bảo mật dữ liệu
Không log dữ liệu nhạy cảm
// ❌ SAI - Log API Key
console.log('Request with API Key:', req.headers['x-api-key'])
// ✅ ĐÚNG - Chỉ log những gì cần thiết
console.log('Payment created:', { trans_id, amount, status })
Validate input
PayerScan đã validate input ở phía server, nhưng bạn cũng nên validate ở phía mình:
// Validate amount (phải là số dương, tối đa 1,000,000 USD)
const amount = parseFloat(req.body.amount)
if (isNaN(amount) || amount <= 0 || amount > 1000000) {
return res.status(400).json({ error: 'Invalid amount' })
}
// Validate callback_url format
if (req.body.callback_url) {
try {
const url = new URL(req.body.callback_url)
if (url.protocol !== 'https:') {
console.warn('callback_url nên dùng HTTPS để bảo mật')
}
} catch {
return res.status(400).json({ error: 'Invalid callback_url' })
}
}
Bảo mật phía máy chủ PayerScan
PayerScan triển khai nhiều lớp bảo mật ở phía server:
- Chống SSRF: Hệ thống xác thực tất cả giá trị
callback_urltrước khi gửi webhook. Các IP nội bộ/riêng tư và URL đáng ngờ bị chặn tự động. - Rate Limiting: Các endpoint API được bảo vệ bằng giới hạn tốc độ burst và sustained để ngăn chặn lạm dụng. Xem Rate Limits để biết chi tiết.
- Validation Input: Tất cả tham số request được validate bằng schema chặt chẽ (phạm vi amount, định dạng URL, giới hạn độ dài chuỗi).
- Webhook Timeout: Webhook callback có timeout 10 giây. Nếu server của bạn không phản hồi trong 10 giây, lần thử bị đánh dấu thất bại và sẽ được thử lại.
Checklist bảo mật
Trước khi go-live
- API Key được lưu trong environment variables (không hardcode)
- API Key không bị lộ ở frontend/client-side code
- File
.envđã thêm vào.gitignore - Webhook endpoint sử dụng HTTPS
- Webhook xác minh
merchant_idcho cả completed và expired - Webhook xác minh
api_keycho webhook completed - Webhook tìm đơn hàng theo
request_idhoặctrans_id - Xử lý webhook idempotent (không xử lý trùng lặp)
- Webhook xác minh
amountkhớp với đơn hàng - Webhook kiểm tra
transaction_hashchưa dùng (chống replay) - Webhook sử dụng atomic update kèm filter status (chống race condition)
- Không log dữ liệu nhạy cảm (API Key, private key)
- Validate input cho amount, URLs
Định kỳ
- Review access logs cho webhook endpoint thường xuyên
- Giám sát các webhook request trái phép hoặc đáng ngờ
- Rotate API Key định kỳ hoặc khi bị lộ
- Cập nhật dependencies có security patches
- Kiểm tra webhook endpoint phản hồi trong 10 giây
Xử lý sự cố bảo mật
Nếu API Key bị lộ
- Ngay lập tức vào trang quản lý Store → Generate New API Key
- Cập nhật API Key mới trên tất cả servers
- Review các API logs gần đây để tìm request trái phép
- Liên hệ support nếu phát hiện giao dịch đáng ngờ
Nếu phát hiện webhook giả mạo
- Xác minh webhook handler đã kiểm tra
merchant_idvàapi_key - Kiểm tra logs xem có
amountkhông khớp hoặctransaction_hashtrùng lặp - Block IP nguồn (nếu xác định được)
- Thêm IP allowlisting nếu có thể
- Liên hệ support để báo cáo sự cố
Liên hệ support
Nếu có vấn đề bảo mật khẩn cấp, liên hệ ngay:
- Email: payerscan@gmail.com