คู่มือ API ร้านค้า
v1 · payin/payout01ภาพรวม
Mazino Pay API ให้ร้านค้าสร้างรายการรับเงิน (PromptPay QR อัตโนมัติ), ตรวจสอบสถานะ, ขอถอนเงิน และรับ webhook แจ้งผลแบบเรียลไทม์ — โครงสร้าง auth เป็นแบบเดียวกับผู้ให้บริการมาตรฐานสากล (คล้าย AstroPay): X-API-Key + X-Signature + X-Timestamp
https://pay.mazinoreader.onlineContent-Type
application/json
02การยืนยันตัวตน
ทุก request ไปยัง Merchant API (/api/deposits, /api/v1/*) ต้องแนบ 3 header นี้เสมอ:
| Header | คำอธิบาย |
|---|---|
X-API-Key | API key ของร้านค้า (สร้างจากพอร์ทัล merchant → API Keys) |
X-Timestamp | Unix timestamp (วินาที) ตอนส่ง request — ต้องอยู่ในช่วง ±5 นาทีจากเวลาเซิร์ฟเวอร์ |
X-Signature | HMAC-SHA256 ของ request เป็น hex string (ดูวิธีคำนวณด้านล่าง) |
ถ้าขาด header ใดหรือ signature ไม่ตรง จะได้ 401 Unauthorized พร้อมเหตุผลใน error
03วิธีเซ็น Signature
สูตรคำนวณ X-Signature:
X-Signature = HMAC_SHA256(
key = secret_key,
data = METHOD + "|" + PATH + "|" + TIMESTAMP + "|" + RAW_BODY
) // hex string
PATH คือ path ล้วน ไม่รวม query string (เช่น /api/deposits) และ RAW_BODY คือ JSON body ดิบตามที่จะส่งจริง (ถ้าไม่มี body ให้เป็นสตริงว่าง)
ตัวอย่าง Node.js
const crypto = require('crypto');
function sign(method, path, timestamp, rawBody, secretKey) {
const base = `${method.toUpperCase()}|${path}|${timestamp}|${rawBody}`;
return crypto.createHmac('sha256', secretKey).update(base).digest('hex');
}
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ merchant_ref: 'ORDER-001', amount: 500 });
const signature = sign('POST', '/api/deposits', timestamp, body, SECRET_KEY);
fetch('https://pay.mazinoreader.online/api/deposits', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
},
body,
});
ตัวอย่าง cURL
TS=$(date +%s)
BODY='{"merchant_ref":"ORDER-001","amount":500}'
SIG=$(node -e "console.log(require('crypto').createHmac('sha256','SECRET_KEY').update('POST|/api/deposits|'+process.argv[1]+'|'+process.argv[2]).digest('hex'))" "$TS" "$BODY")
curl -X POST https://pay.mazinoreader.online/api/deposits \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_xxx" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"
04สร้างรายการฝาก (Deposit)
สร้างรายการรับเงินใหม่ ระบบจะออก PromptPay QR ให้ลูกค้าโอนเข้า และแจ้งผลผ่าน webhook เมื่อยืนยันยอดสำเร็จ
Request Body
{
"merchant_ref": "ORDER-001", // รหัสอ้างอิงของร้านค้า ต้องไม่ซ้ำ
"amount": 500, // จำนวนเงิน (บาท)
"currency": "THB" // ไม่บังคับ รองรับแค่ THB
}
Response 200
{
"ok": true,
"ref": "NTV-a1b2c3d4",
"merchant_ref": "ORDER-001",
"amount": 500,
"amount_expected": 500.12, // ยอดจริงที่ต้องโอน (+สตางค์กันชนเพื่อ auto-match)
"fee": 7.50,
"net": 492.50,
"qr_payload": "00020101021129...", // เอาไปสร้าง QR image เองก็ได้ หรือ render จาก payload นี้
"expires_at": "2026-09-10 14:30:00",
"status": "pending"
}
05เช็คสถานะรายการ
:ref ใช้ได้ทั้ง ref ของเรา (NTV-.../WTV-...) และ merchant_ref ของร้านค้าเอง
Response 200
{
"ok": true,
"ref": "NTV-a1b2c3d4",
"merchant_ref": "ORDER-001",
"kind": "deposit",
"status": "completed", // pending | completed | failed | refunded
"amount": 500,
"amount_paid": 500.12,
"fee": 7.50,
"net": 492.50,
"paid_at": "2026-09-10 14:12:41"
}
06ยอดคงเหลือ
{
"ok": true,
"merchant_code": "ACME01",
"currency": "THB",
"balance": 12480.50,
"available_balance": 11980.50,
"pending_balance": 500.00
}
07ขอถอนเงิน (Withdraw)
คำขอถอนจะถูกพักไว้เป็น pending และหักยอดไปกอง pending_balance ทันที รอแอดมินอนุมัติ ไม่ใช่การโอนอัตโนมัติ
Request Body
{
"merchant_ref": "PAYOUT-001",
"amount": 1000,
"account_name": "สมชาย ใจดี",
"account_no": "1234567890",
"bank": "kbank"
}
Response 200
{
"ok": true,
"ref": "WTV-9f8e7d6c",
"merchant_ref": "PAYOUT-001",
"status": "pending",
"amount": 1000,
"fee": 10,
"net": 990,
"total_debit": 1010
}
08Callback (Webhook)
เมื่อรายการฝาก/ถอนเปลี่ยนสถานะเป็น completed ระบบจะยิง POST ไปที่ callback_url ที่ตั้งไว้ในโปรไฟล์ร้านค้า พร้อม header X-Mazino-Signature (HMAC-SHA256 ของ body ทั้งก้อน เซ็นด้วย callback_secret ของร้านค้า) — retry อัตโนมัติสูงสุด 3 ครั้งถ้าไม่ได้ 2xx กลับมา แล้วมี recovery worker ยิงซ้ำต่อในพื้นหลังอีกสูงสุด 24 ชม.
Payload ที่ส่งมา
{
"event": "deposit.completed",
"ref": "NTV-a1b2c3d4",
"merchant_ref": "ORDER-001",
"status": "completed",
"amount": 500,
"amount_paid": 500.12,
"fee": 7.50,
"net": 492.50,
"currency": "THB",
"via": "sms_match",
"paid_at": "2026-09-10 14:12:41"
}
วิธีตรวจสอบ signature (Node.js)
const crypto = require('crypto');
function isValidCallback(rawBody, signatureHeader, callbackSecret) {
const expected = crypto.createHmac('sha256', callbackSecret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
ตอบกลับด้วย HTTP 200 ภายใน 10 วินาทีเพื่อยืนยันว่ารับ webhook แล้ว ไม่งั้นระบบจะยิงซ้ำ
09รหัสข้อผิดพลาด
| HTTP | ความหมาย |
|---|---|
400 | Body ไม่ครบ / จำนวนเงินนอกช่วงที่กำหนด |
401 | API Key ไม่ถูกต้อง หรือ Signature/Timestamp ไม่ผ่าน |
403 | บัญชีร้านค้าถูกระงับ หรือ IP ไม่อยู่ใน whitelist |
404 | ไม่พบรายการที่อ้างอิง |
409 | merchant_ref ซ้ำ (idempotent — คืนรายการเดิม) |
500 | ข้อผิดพลาดฝั่งเซิร์ฟเวอร์ — ดู error ในผลลัพธ์ |
10Sandbox / ทดสอบ
สร้าง API key โหมด test จากพอร์ทัลร้านค้า (นำหน้าด้วย pk_test_) ใช้ flow และ header เดียวกันทุกอย่างกับ live เพียงแต่ไม่กระทบยอดเงินจริง เหมาะสำหรับ integrate ก่อนขึ้น production
สมัคร/รับ API Key + Secret Key
จากพอร์ทัล merchant → แท็บ API Keys → สร้างใหม่ (โหมด test หรือ live)
ตั้งค่า callback_url
แจ้งแอดมินตั้ง URL รับ webhook + callback_secret ในโปรไฟล์ร้านค้า
ยิง POST /api/deposits ทดสอบ
ใช้ตัวอย่าง cURL/Node ด้านบน เซ็น signature ให้ถูกต้องก่อนยิงจริง
เปลี่ยนไป live key เมื่อพร้อม
โครงสร้าง request เหมือนเดิมทั้งหมด เปลี่ยนแค่ API key/secret