คู่มือ API ร้านค้า

v1 · payin/payout

01ภาพรวม

Mazino Pay API ให้ร้านค้าสร้างรายการรับเงิน (PromptPay QR อัตโนมัติ), ตรวจสอบสถานะ, ขอถอนเงิน และรับ webhook แจ้งผลแบบเรียลไทม์ — โครงสร้าง auth เป็นแบบเดียวกับผู้ให้บริการมาตรฐานสากล (คล้าย AstroPay): X-API-Key + X-Signature + X-Timestamp

Base URL
https://pay.mazinoreader.online
Content-Type
application/json

02การยืนยันตัวตน

ทุก request ไปยัง Merchant API (/api/deposits, /api/v1/*) ต้องแนบ 3 header นี้เสมอ:

Headerคำอธิบาย
X-API-KeyAPI key ของร้านค้า (สร้างจากพอร์ทัล merchant → API Keys)
X-TimestampUnix timestamp (วินาที) ตอนส่ง request — ต้องอยู่ในช่วง ±5 นาทีจากเวลาเซิร์ฟเวอร์
X-SignatureHMAC-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)

POST/api/depositsสร้าง QR PromptPay อัตโนมัติ

สร้างรายการรับเงินใหม่ ระบบจะออก 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เช็คสถานะรายการ

GET/api/v1/transactions/:ref

: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ยอดคงเหลือ

GET/api/v1/balance
{
  "ok": true,
  "merchant_code": "ACME01",
  "currency": "THB",
  "balance": 12480.50,
  "available_balance": 11980.50,
  "pending_balance": 500.00
}

07ขอถอนเงิน (Withdraw)

POST/api/v1/withdrawalsเข้าคิวรออนุมัติ

คำขอถอนจะถูกพักไว้เป็น 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ความหมาย
400Body ไม่ครบ / จำนวนเงินนอกช่วงที่กำหนด
401API Key ไม่ถูกต้อง หรือ Signature/Timestamp ไม่ผ่าน
403บัญชีร้านค้าถูกระงับ หรือ IP ไม่อยู่ใน whitelist
404ไม่พบรายการที่อ้างอิง
409merchant_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