คู่มือความปลอดภัย
แนวทางปฏิบัติที่ดีที่สุดเพื่อรักษาความปลอดภัยในการเชื่อมต่อ PayerScan Payment Gateway
ความปลอดภัยของ API Key
ห้ามเปิดเผย API Key ของคุณ
API Key ใช้สำหรับยืนยันตัวตนคำขอที่ส่งไปยัง PayerScan API จะต้องไม่ให้ผู้ใช้ปลายทางมองเห็นได้
// ❌ ผิด - เรียก API จาก frontend (API Key ถูกเปิดเผยให้ทุกคน)
fetch('https://api.payerscan.com/payment/crypto', {
headers: { 'x-api-key': 'EQnhBYpknGAP...' } // API Key ถูกเปิดเผย!
})
// ✅ ถูกต้อง - เรียก API จากเซิร์ฟเวอร์ backend
// Frontend เรียก API ภายในของคุณ
fetch('/api/create-payment', { body: { amount: 100 } })
// Backend ของคุณเรียก PayerScan (API Key ปลอดภัยบนเซิร์ฟเวอร์)
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)
})
จัดเก็บ API Key อย่างปลอดภัย
# ✅ ใช้ environment variables
PAYERSCAN_API_KEY=EQnhBYpknGAP...
# ❌ ห้าม hardcode ในโค้ด
const API_KEY = 'EQnhBYpknGAP...' # อย่าทำแบบนี้
หมายเหตุ: อย่า commit API Key เข้าสู่ระบบ version control (Git) ให้เพิ่ม
.envลงในไฟล์.gitignore
หมุนเวียน API Key เป็นระยะ
- เปลี่ยน API Key หากสงสัยว่าถูกเปิดเผย
- ไปที่การจัดการร้านค้า → สร้าง API Key ใหม่
- อัปเดต API Key ใหม่บนเซิร์ฟเวอร์ทันที
- API Key เดิมจะถูกยกเลิกทันทีหลังจากสร้าง Key ใหม่
ความปลอดภัยของ Webhook
ใช้ HTTPS
ใช้ HTTPS สำหรับ callback_url เสมอเพื่อเข้ารหัสข้อมูลที่ส่งและป้องกันการโจมตีแบบ man-in-the-middle
// ✅ ถูกต้อง - ใช้ HTTPS
callback_url: 'https://your-server.com/webhook/payment'
// ❌ ผิด - ใช้ HTTP (ข้อมูลไม่ถูกเข้ารหัส เสี่ยงต่อการถูกดักฟัง)
callback_url: 'http://your-server.com/webhook/payment'
ตรวจสอบคำขอ Webhook
ตรวจสอบเสมอว่าคำขอ webhook มาจาก 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. ตรวจสอบ merchant_id (ทั้ง completed และ expired)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).json({ error: 'Invalid merchant' })
}
// 2. ตรวจสอบ api_key (เฉพาะ completed — expired ไม่มี api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).json({ error: 'Invalid API Key' })
}
// 3. ค้นหาคำสั่งซื้อตาม request_id (ถ้ามี) หรือ 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. ประมวลผลเฉพาะคำสั่งซื้อที่อยู่ในสถานะที่สามารถอัปเดตได้ (idempotent)
const processableStatuses = ['waiting', 'pending', 'processing']
if (!processableStatuses.includes(order.status)) {
return res.status(200).json({ message: 'Already processed' })
}
// 5. ตรวจสอบว่าจำนวนเงินตรงกัน (เฉพาะ completed — ป้องกันการปลอมแปลง)
if (status === 'completed') {
if (parseFloat(amount) !== parseFloat(order.amount)) {
return res.status(400).json({ error: 'Amount mismatch' })
}
}
// 6. ตรวจสอบว่า transaction_hash ยังไม่ถูกใช้ (ป้องกัน 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 — รวม filter สถานะเพื่อป้องกัน 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' })
}
})
การประมวลผลแบบ Idempotent
Webhook อาจถูกส่งซ้ำได้สูงสุด 5 ครั้ง ตรวจสอบให้แน่ใจว่าการประมวลผลหลายครั้งปลอดภัย:
// ✅ ถูกต้อง - อัปเดตเฉพาะคำสั่งซื้อที่อยู่ในสถานะที่สามารถประมวลผลได้
const processableStatuses = ['waiting', 'pending', 'processing']
if (!processableStatuses.includes(order.status)) {
return res.status(200).json({ message: 'Already processed' })
}
// ❌ ผิด - ไม่มีการตรวจสอบ อาจเพิ่มยอดเงินหลายครั้ง
await addBalance(user, amount) // ถ้า webhook ส่ง 2 ครั้ง → เพิ่มยอด 2 ครั้ง!
ความปลอดภัยของข้อมูล
ห้าม log ข้อมูลที่เป็นความลับ
// ❌ ผิด - Log API Key
console.log('Request with API Key:', req.headers['x-api-key'])
// ✅ ถูกต้อง - Log เฉพาะสิ่งที่จำเป็น
console.log('Payment created:', { trans_id, amount, status })
ตรวจสอบข้อมูลนำเข้า
PayerScan ตรวจสอบข้อมูลนำเข้าฝั่งเซิร์ฟเวอร์ แต่คุณควรตรวจสอบฝั่งของคุณด้วย:
// ตรวจสอบจำนวนเงิน (ต้องเป็นค่าบวก สูงสุด 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' })
}
// ตรวจสอบรูปแบบ callback_url
if (req.body.callback_url) {
try {
const url = new URL(req.body.callback_url)
if (url.protocol !== 'https:') {
console.warn('callback_url ควรใช้ HTTPS เพื่อความปลอดภัย')
}
} catch {
return res.status(400).json({ error: 'Invalid callback_url' })
}
}
การป้องกันฝั่งเซิร์ฟเวอร์ PayerScan
PayerScan ใช้การป้องกันหลายชั้นฝั่งเซิร์ฟเวอร์:
- การป้องกัน SSRF: ระบบตรวจสอบค่า
callback_urlทั้งหมดก่อนส่ง webhook IP ภายใน/ส่วนตัวและ URL ที่น่าสงสัยจะถูกบล็อกโดยอัตโนมัติ - การจำกัดอัตรา: Endpoint API ได้รับการป้องกันด้วยการจำกัดอัตราแบบ burst และ sustained เพื่อป้องกันการใช้งานในทางที่ผิด ดูรายละเอียดที่ การจำกัดอัตรา
- การตรวจสอบข้อมูลนำเข้า: พารามิเตอร์คำขอทั้งหมดถูกตรวจสอบด้วย schema ที่เข้มงวด (ช่วงจำนวนเงิน รูปแบบ URL ขีดจำกัดความยาวสตริง)
- Webhook Timeout: Webhook callback มี timeout 10 วินาที หากเซิร์ฟเวอร์ของคุณไม่ตอบกลับภายใน 10 วินาที ความพยายามนั้นจะถูกทำเครื่องหมายว่าล้มเหลวและจะลองใหม่
รายการตรวจสอบความปลอดภัย
ก่อนเปิดใช้งาน
- API Key ถูกเก็บใน environment variables (ไม่ hardcode)
- API Key ไม่ถูกเปิดเผยในโค้ดฝั่ง frontend/client
- เพิ่มไฟล์
.envใน.gitignoreแล้ว - Endpoint webhook ใช้ HTTPS
- Webhook ตรวจสอบ
merchant_idทั้ง completed และ expired - Webhook ตรวจสอบ
api_keyสำหรับ webhook completed - Webhook ค้นหาคำสั่งซื้อตาม
request_idหรือtrans_id - การจัดการ webhook เป็น idempotent (ไม่ประมวลผลซ้ำ)
- Webhook ตรวจสอบว่า
amountตรงกับจำนวนเงินในคำสั่งซื้อ - Webhook ตรวจสอบว่า
transaction_hashยังไม่ถูกใช้ (ป้องกัน replay) - Webhook ใช้ atomic update พร้อม filter สถานะ (ป้องกัน race condition)
- ไม่บันทึกข้อมูลที่ละเอียดอ่อน (API Key, private key)
- ตรวจสอบความถูกต้องของอินพุตสำหรับจำนวนเงินและ URL
ดำเนินการอย่างต่อเนื่อง
- ตรวจสอบ access log ของ endpoint webhook เป็นประจำ
- เฝ้าระวังคำขอ webhook ที่ไม่ได้รับอนุญาตหรือน่าสงสัย
- หมุนเวียน API Key เป็นระยะหรือเมื่อถูกเปิดเผย
- อัปเดต dependencies ที่มี security patches
- ทดสอบว่า endpoint webhook ตอบกลับภายใน 10 วินาที
การตอบสนองต่อเหตุการณ์ด้านความปลอดภัย
หาก API Key ถูกเปิดเผย
- ทันที ไปที่การจัดการร้านค้า → สร้าง API Key ใหม่
- อัปเดต API Key ใหม่บนเซิร์ฟเวอร์ทั้งหมด
- ตรวจสอบ API log ล่าสุดเพื่อหาคำขอที่ไม่ได้รับอนุญาต
- ติดต่อฝ่ายสนับสนุนหากพบธุรกรรมที่น่าสงสัย
หากตรวจพบ webhook ปลอม
- ตรวจสอบว่า webhook handler ของคุณตรวจสอบ
merchant_idและapi_key - ตรวจสอบ logs สำหรับ
amountไม่ตรงกันหรือtransaction_hashซ้ำ - บล็อก IP ต้นทาง (หากระบุได้)
- เพิ่ม IP allowlisting หากเป็นไปได้
- ติดต่อ support เพื่อรายงานเหตุการณ์
ติดต่อฝ่ายสนับสนุน
สำหรับปัญหาความปลอดภัยเร่งด่วน ติดต่อเราทันที:
- อีเมล: payerscan@gmail.com