Webhook
Webhook ช่วยให้ระบบของคุณรับการแจ้งเตือนแบบเรียลไทม์อัตโนมัติจาก PayerScan ระบบจะส่งคำขอ POST ไปยัง callback_url ที่ร้านค้าระบุไว้เมื่อสร้างใบแจ้งหนี้
ประเภทเหตุการณ์
PayerScan มี 2 เหตุการณ์หลักที่ส่งกลับในฟิลด์ status:
completed— ชำระเงินสำเร็จ: มีการยืนยันธุรกรรมที่ตรงกับใบแจ้งหนี้แล้ว (blockchain หรือ Binance Pay)expired— ใบแจ้งหนี้หมดอายุ: ใบแจ้งหนี้เกินเวลาที่กำหนดสำหรับการชำระเงิน
ร้านค้าต้องเปิด URL สาธารณะ (แนะนำ HTTPS) รับคำขอ POST ประมวลผล payload และ ส่งกลับ HTTP 2xx ภายในเวลา timeout ที่กำหนด (เช่น 10 วินาที)
โครงสร้าง Payload
1. Webhook เมื่อชำระเงินสำเร็จ (status: completed)
เมื่อพบธุรกรรมที่ตรงกัน (ที่อยู่ถูกต้อง จำนวนเงินถูกต้อง อยู่ในกรอบเวลา) ระบบจะอัปเดตใบแจ้งหนี้เป็น completed และส่ง POST ไปยัง callback_url พร้อม payload ดังนี้:
Payload (JSON body)
{
"merchant_id": "MERCHANT_001",
"api_key": "YOUR_API_KEY",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "completed",
"amount": "100",
"token_symbol": "USDT",
"token_price": "1",
"token_amount": "100.0026",
"network_symbol": "BSC",
"from_address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3",
"to_address": "0xc221460115e2CfCa5bF089A7e647b11cb9631efE",
"transaction_hash": "0xd346b32b83b35376d42a2464598fbf565fffb39e6569200f034a5e8342c532d7"
}
| ฟิลด์ | คำอธิบาย |
|---|---|
merchant_id | รหัสร้านค้า |
api_key | API Key (สำหรับตรวจสอบข้ามฝั่งหากต้องการ) |
request_id | รหัสคำสั่งซื้อของคุณ (สำหรับเปรียบเทียบกับใบแจ้งหนี้ภายในของคุณ) |
trans_id | รหัสใบแจ้งหนี้ ใช้เพื่ออัปเดตคำสั่งซื้อที่ตรงกัน |
status | เป็น "completed" เสมอ |
amount | จำนวนเงิน USD (string) |
token_symbol | เหรียญ/โทเค็น (เช่น USDT) |
token_price | อัตราแลกเปลี่ยน ณ เวลาทำธุรกรรม (string) อาจ fallback เป็น amount หากไม่มีข้อมูล |
token_amount | จำนวนคริปโตจริงที่โอน (string) อาจเป็น null ในบางกรณีที่หายาก |
network_symbol | เครือข่าย (เช่น BSC, TRC20, ETH, SOL, BINANCE_ID, OKX_UID, BYBIT_UID, ...) |
from_address | ที่อยู่กระเป๋าเงินผู้ส่ง |
to_address | ที่อยู่กระเป๋าเงินผู้รับ (ของร้านค้า) |
transaction_hash | แฮชธุรกรรมบน blockchain |
ข้อกำหนดสำหรับ Merchant
- รับ POST ที่ endpoint ที่ตรงกับ
callback_url - แยกวิเคราะห์ JSON body
- ตรวจสอบ (ไม่บังคับแต่แนะนำ): เปรียบเทียบ
merchant_idหรือtrans_idกับข้อมูลภายในของคุณ - อัปเดตคำสั่งซื้อ: ทำเครื่องหมายคำสั่งซื้อที่ตรงกับ
trans_idหรือrequest_idว่าชำระแล้ว; บันทึกtransaction_hashหากต้องการ - ส่งกลับ HTTP 2xx (เช่น 200 OK) ภายใน timeout (เช่น 10 วินาที) ไม่จำเป็นต้องมี response body; ระบบต้องการเพียงสถานะ 2xx เพื่อถือว่า webhook สำเร็จ
หากคุณส่งกลับ 4xx/5xx หรือ timeout ระบบจะทำเครื่องหมาย callback เป็น failed และมีกลไก ลองใหม่ (จำนวนครั้งและช่วงเวลาขึ้นอยู่กับการตั้งค่า backend)
2. Webhook เมื่อใบแจ้งหนี้หมดอายุ (status: expired)
เมื่อใบแจ้งหนี้เปลี่ยนเป็นสถานะ expired (เกิน expires_at) ระบบจะส่ง POST ไปยัง callback_url พร้อม payload ที่ง่ายกว่า:
Payload (JSON body)
{
"merchant_id": "MERCHANT_001",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "expired",
"amount": "100"
}
| ฟิลด์ | คำอธิบาย |
|---|---|
merchant_id | รหัสร้านค้า |
request_id | รหัสคำสั่งซื้อของคุณ (หากระบุไว้ตอนสร้างใบแจ้งหนี้) |
trans_id | รหัสใบแจ้งหนี้ ใช้เพื่ออัปเดตคำสั่งซื้อที่ตรงกัน |
status | เป็น "expired" เสมอ |
amount | จำนวนเงิน USD (string) |
คุณสามารถใช้ข้อมูลนี้เพื่ออัปเดตคำสั่งซื้อเป็น "หมดอายุ" ยกเลิกการจอง ฯลฯ คุณยังควรส่งกลับ HTTP 2xx เพื่อไม่ให้ระบบถือว่าเป็นข้อผิดพลาด
3. ตัวอย่างการจัดการ Webhook (Node.js)
app.post('/webhook/payment', express.json(), async (req, res) => {
const { merchant_id, api_key, trans_id, request_id, status, amount, transaction_hash } = req.body;
// ตรวจสอบ merchant_id (ทั้ง completed และ expired)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).send('Unauthorized');
}
// ตรวจสอบ api_key (เฉพาะ completed — expired ไม่มี api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).send('Unauthorized');
}
// ค้นหาคำสั่งซื้อตาม request_id หรือ trans_id
const order = await orderService.findByRequestId(request_id) || await orderService.findByTransId(trans_id);
if (!order) {
return res.status(404).send('Order not found');
}
if (status === 'completed') {
await orderService.markPaid(order, { transaction_hash, amount });
} else if (status === 'expired') {
await orderService.markExpired(order);
}
res.status(200).send('OK');
});
การตรวจสอบลายเซ็น
เพื่อให้แน่ใจว่า webhook มาจาก PayerScan จริง คุณควรตรวจสอบลายเซ็นหรือตรวจสอบข้อมูลข้ามฝั่ง
- HTTPS: ใช้ HTTPS สำหรับ
callback_urlเสมอเพื่อป้องกันการดักฟัง - ตรวจสอบ API Key (เฉพาะ completed): Webhook
completedจะมีapi_keyอยู่ใน payload เปรียบเทียบกับ API key ที่คุณเก็บไว้เพื่อยืนยันความถูกต้อง หมายเหตุ: webhookexpiredไม่มีapi_key
ความเป็น Idempotent
trans_id หนึ่งจะได้รับเหตุการณ์ completed เพียงครั้งเดียว แต่กลไกลองใหม่อาจส่งซ้ำ ตรวจสอบให้แน่ใจว่าตรรกะการประมวลผลของคุณมีความ idempotent (อัปเดตหลายครั้งปลอดภัย ไม่เครดิตซ้ำ)
ลองใหม่เมื่อ Webhook ล้มเหลว
เมื่อเซิร์ฟเวอร์ของร้านค้าส่งกลับข้อผิดพลาด (4xx/5xx) หรือ timeout ระบบจะลองส่ง webhook ใหม่ตามตารางดังนี้:
| ลำดับ | เวลา |
|---|---|
| ครั้งที่ 1 | ทันที (เมื่อใบแจ้งหนี้เสร็จสมบูรณ์) |
| ครั้งที่ 2 | หลังจาก 10 วินาที |
| ครั้งที่ 3 | หลังจาก 30 วินาที |
| ครั้งที่ 4 | หลังจาก 30 วินาที |
| ครั้งที่ 5 | หลังจาก 60 วินาที |
สูงสุด 5 ครั้ง หลังจากล้มเหลว 5 ครั้ง callback_status จะเปลี่ยนเป็น failed และไม่มีการลองใหม่อีก
ร้านค้าควรตรวจสอบให้แน่ใจว่า endpoint มีเสถียรภาพและส่งกลับ 2xx อย่างรวดเร็ว (< 10 วินาที) เพื่อหลีกเลี่ยงการลองใหม่ที่ไม่จำเป็น