← ภาพรวมบัญชี · ฝาก–ถอนให้ลูกค้า · เชื่อม LINE OA
Merchant Integration Guideอ่านจบหน้าเดียว เชื่อมต่อได้ด้วยตัวเอง — ระบบเดิมของร้านยังทำงานตามปกติทุกอย่าง
ร้านใหม่ต้องตั้งค่าบัญชีรับเงินและหลักประกันให้พร้อมก่อนรับรายการ การใช้ผ่าน LINE ต้องตั้งค่าและเปิด LINE OA ของร้านแยกต่างหาก ส่วนคู่มือ API สำหรับเชื่อมระบบอยู่ด้านล่าง
ระบบจับคู่ "ลูกค้าที่กำลังฝาก" กับ "ลูกค้าที่กำลังถอน" ให้โอนเงินถึงกันโดยตรง (peer-to-peer) พร้อม escrow และตรวจสลิปอัตโนมัติ ผลคือ:
| Mode B — เต็มรูปแบบ | Mode A — เสริมระบบเดิม (overlay) | |
|---|---|---|
| เหมาะกับ | ร้านที่อยากให้เราดูแลทุกรายการ | ร้านที่มีระบบฝาก–ถอนออโต้อยู่แล้ว |
| วิธีทำงาน | ส่งทุกรายการเข้าระบบเรา เราจับคู่และดูแลจนจบ | ถามเราก่อนว่า "ตอนนี้มีคู่ไหม?" มีคู่ = ยกให้เรา ไม่มี = ใช้ระบบร้านตามเดิม |
| ไม่มีคู่ | ร้านรับ/จ่ายแทนอัตโนมัติ (ข้อ 8) | ไม่มีอะไรเกิดขึ้นในระบบเราเลย |
| Endpoint หลัก | POST /api/deposit, POST /api/withdraw | POST /api/deposit/try, POST /api/withdraw/try |
| สิ่งที่ได้รับ | ใช้ทำอะไร |
|---|---|
BASE_URL | ที่อยู่ API — สำหรับระบบนี้คือ (โหลดอัตโนมัติ) |
| ชื่อร้าน (merchant name) | ใช้เป็น username ตอน login จัดการตั้งค่า |
API_KEY | ใส่ header X-API-Key ทุกครั้งที่ยิง API (ความลับสูงสุด) |
WEBHOOK_SECRET | ใช้ตรวจลายเซ็น webhook ที่เราส่งไปหาร้าน |
ทุก endpoint รองรับ header x-sandbox: true สำหรับทดสอบ ไม่กระทบยอดเงินใด ๆ
# 1) สร้างรายการฝากทดสอบ
curl -X POST "{{BASE}}/api/deposit" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "x-sandbox: true" \
-d '{
"merchantUserId": "customer-001",
"amount": "100.0000",
"idempotencyKey": "test-dep-001"
}'
# ตอบกลับ: { "id": 123, "uuid": "…", "status": "WAITING_PAYMENT", … }
# 2) เปิดหน้ารายการ (ลูกค้าเห็นบัญชีปลายทาง + ปุ่มแนบสลิป)
curl "{{BASE}}/api/checkout/<uuid>"
# 3) จำลองว่าลูกค้าโอนแล้ว (ต้องแนบ uuid เป็นหลักฐานการครอบครองรายการ)
curl -X POST "{{BASE}}/api/transaction/<id>/transferred" \
-H "Content-Type: application/json" \
-d '{ "uuid": "<uuid>" }'
# 4) เช็คสถานะ
curl "{{BASE}}/api/transaction/<id>"
ผู้ดูแลระบบจะเติม gas/credit เริ่มต้นให้ตามที่ตกลง — ถ้าเจอ
Insufficient Gas Balance / Insufficient Credit Balance แปลว่าต้องเติมวงเงินก่อน
curl -X POST "{{BASE}}/api/auth/login" \
-H "Content-Type: application/json" \
-d '{ "username": "<ชื่อร้าน>", "password": "<API_KEY>" }'
# ตอบกลับ: { "token": "…", "role": "merchant", … } (อายุ 24 ชม.)
ต้องส่งครบ 3 อย่าง: Bearer token (จาก 5.1) + X-API-Key + confirmApiKey
(ยืนยัน key ซ้ำใน body — กันการแก้ค่าการเงินโดยไม่ตั้งใจ):
curl -X PATCH "{{BASE}}/api/merchant/settings" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://your-system.example.com/webhooks/gateway",
"bankName": "SCB",
"bankAccountNo": "111-2-33333-4",
"bankAccountName": "Your Shop Co., Ltd.",
"confirmApiKey": "<API_KEY>"
}'
ฟิลด์ที่ตั้งเองได้: webhookUrl, bankName, bankAccountNo (ระบบเข้ารหัสเก็บให้),
bankAccountName, allowedIps, webhookEncryptionEnabled, autoVerifySlip,
notificationEmail, webhookRetryInterval, checkoutLogoUrl, checkoutPrimaryColor
curl -X POST "{{BASE}}/api/merchant/rotate-api-key" \
-H "X-API-Key: $API_KEY" \
-d '{ "expiresHours": 24 }'
# ได้ key ใหม่ทันที; key เก่าใช้ต่อได้ตามชั่วโมงที่กำหนด (transition period)
ทุกครั้งที่รายการเปลี่ยนสถานะ เรา POST ไปที่ webhookUrl ของร้าน:
{
"event": "transaction.completed",
"data": {
"id": 123, "type": "DEPOSIT", "amount": "100.0000",
"status": "COMPLETED", "merchantId": 1, "userId": 45,
"idempotencyKey": "…", "createdAt": "…"
}
}
Event หลัก: transaction.waiting_payment, transaction.paid_unconfirmed,
transaction.completed, transaction.cancelled, transaction.disputed,
transaction.gas_settled, payout.required (ข้อ 8) และ
confirmation.reminder — ระบบทักถามอัตโนมัติเมื่อฝั่งผู้ถอนยังไม่กดยืนยันเกิน 10 นาที
(ตั้งค่าได้) ร้านใช้ event นี้เด้งถามลูกค้าว่า "ได้รับเงินแล้วหรือยัง?" โดย
data.uuid พาลูกค้าไปหน้ารายการได้โดยตรง
Header X-Webhook-Signature = HMAC-SHA256 ของ raw body ด้วย WEBHOOK_SECRET:
import crypto from 'crypto';
import express from 'express';
const app = express();
app.post('/webhooks/gateway', express.json({
verify: (req, _res, buf) => { req.rawBody = buf; },
}), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.rawBody).digest('hex');
const got = req.get('X-Webhook-Signature') || '';
if (got.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.status(401).end();
}
res.status(200).end(); // ตอบ 200 ให้เร็ว แล้วค่อยประมวลผลต่อ
handleEvent(req.body); // ทำงานหนักแบบ async
});
GET /api/merchant/webhook-logs และ
POST /api/merchant/webhook-logs/:id/resendwebhookEncryptionEnabled = payload เข้ารหัส AES-256-GCM อีกชั้น
(ถอดด้วย key = SHA-256 ของ WEBHOOK_SECRET)POST /api/deposit (X-API-Key)
body: { merchantUserId, amount, idempotencyKey, redirectUrl?, slipUrl? }
Flow: สร้างรายการ → ระบบจับคู่กับคนถอน (ปกติภายในวินาที) →
ร้านพาลูกค้าไปหน้า GET /api/checkout/<uuid> (มีเลขบัญชีปลายทาง + ปุ่มแนบสลิป) →
ลูกค้าโอน + กด "โอนแล้ว" → ระบบตรวจสลิป/รอผู้ถอนยืนยัน → webhook completed →
ร้านเติมเครดิตให้ลูกค้าในระบบร้าน
slipUrl (URL รูปหรือ data:base64) ตั้งแต่ตอนสร้าง → ตรวจผ่าน = settle ทันที (instant)POST /api/withdraw (X-API-Key)
body: { merchantUserId, amount, idempotencyKey,
bankName, bankAccountNo, bankAccountName } ← บัญชีของลูกค้าผู้รับเงิน
Flow: ระบบจับคู่กับคนฝาก → คนฝากโอนเข้าบัญชีลูกค้าของร้านโดยตรง →
สลิปผ่าน/ผู้ถอนกดยืนยัน/ครบหน้าต่างโต้แย้ง → webhook completed
POST /api/transaction/:id/release)POST /api/transaction/:id/dispute เครดิตถูกล็อกไว้ให้ตรวจสอบสำหรับร้านที่มีระบบโอนเงินอัตโนมัติ: เมื่อ withdraw ไม่มีคู่ (หรือเหลือเศษ)
ระบบจะยิง webhook payout.required บอกร้านว่า "โอน X บาท ไปบัญชีนี้":
{
"event": "payout.required",
"data": {
"id": 456, "payoutRef": "b1f0-…", "payoutAmount": "300.0000",
"bankName": "KBank", "bankAccountNo": "123-4-56789-0",
"bankAccountName": "Somchai J."
}
}
ระบบร้านโอนเงินแล้วยืนยันกลับ:
curl -X POST "{{BASE}}/api/transaction/456/payout-confirm" \
-H "X-API-Key: $API_KEY" \
-d '{ "payoutRef": "b1f0-…", "bankRef": "<เลขอ้างอิงโอนจากธนาคารร้าน>" }'
payoutAmount เสมอ (อาจเป็นแค่เศษที่เหลือ ไม่ใช่ยอดเต็ม)payoutRef = idempotency key — dedupe ฝั่งร้านด้วยค่านี้ ยืนยันซ้ำไม่มีผลข้างเคียงก่อนร้านจะประมวลผลด้วยระบบตัวเอง ถามเราหนึ่งครั้ง:
# ฝั่งถอน: มีคนฝากรออยู่พอดีไหม?
curl -X POST "{{BASE}}/api/withdraw/try" \
-H "X-API-Key: $API_KEY" \
-d '{ "merchantUserId": "c1", "amount": "500.0000",
"idempotencyKey": "try-w-001",
"bankName": "KBank", "bankAccountNo": "…", "bankAccountName": "…" }'
# matched:true → เราจัดการ P2P ให้ทั้งรายการ (ติดตามผลทาง webhook)
# matched:false → "ไม่มีอะไรถูกสร้างเลย" ร้านโอนเองด้วยระบบเดิมได้ทันที
# ฝั่งฝาก: มีคนถอนรอรับพอดีไหม?
curl -X POST "{{BASE}}/api/deposit/try" -H "X-API-Key: $API_KEY" \
-d '{ "merchantUserId": "c2", "amount": "500.0000", "idempotencyKey": "try-d-001" }'
# matched:true → ได้ bankDetails ของผู้รับ ให้ลูกค้าโอนตรง
# matched:false → ให้ลูกค้าโอนเข้าบัญชีร้านตามระบบเดิม
matched:false = ไม่มีร่องรอยค้างในระบบ
ใช้ idempotencyKey เดิมลองใหม่ได้เสมอmatched:false
บ่อยกว่าปกติ ซึ่งไม่ใช่ความผิดพลาด: ร้านใช้ระบบเดิมของตัวเองไปตามปกติ
ส่วนการจับคู่ภายในร้านเดียวกันใช้ได้เต็มที่ตั้งแต่วันแรก| ตัวเลือก | วิธีเปิด | ผล |
|---|---|---|
| IP allowlist | allowedIps: "1.2.3.4, 10.0.0.0/24" ใน settings | ยิง API ได้เฉพาะจาก IP ร้าน |
| Webhook encryption | webhookEncryptionEnabled: true | payload เข้ารหัส AES-256-GCM |
| Key rotation | POST /api/merchant/rotate-api-key | เปลี่ยน key โดยไม่มี downtime |
idempotencyKey ฝั่งร้าน: หนึ่งรายการ = หนึ่ง key ไม่ใช้ซ้ำข้ามยอด| Method | Path | Auth | ใช้ทำอะไร |
|---|---|---|---|
| POST | /api/deposit | X-API-Key | สร้างรายการฝาก |
| POST | /api/withdraw | X-API-Key | สร้างรายการถอน |
| POST | /api/deposit/try | X-API-Key | ถามจับคู่ฝากแบบทันที (Mode A) |
| POST | /api/withdraw/try | X-API-Key | ถามจับคู่ถอนแบบทันที (Mode A) |
| GET | /api/checkout/:idOrUuid | — | หน้ารายการสำหรับลูกค้า |
| POST | /api/transaction/:id/transferred | uuid หรือ X-API-Key | แจ้ง "โอนแล้ว" (+สลิป) |
| POST | /api/transaction/:id/release | X-API-Key | ยืนยันรับเงิน/ปล่อยเครดิต |
| POST | /api/transaction/:id/dispute | X-API-Key | เปิดข้อโต้แย้ง |
| POST | /api/transaction/:id/payout-confirm | X-API-Key | ยืนยันจ่ายแทนแล้ว (auto-payout) |
| GET | /api/transaction/:id | — | เช็คสถานะรายการ |
| POST | /api/auth/login | name+key | รับ session token จัดการตั้งค่า |
| PATCH | /api/merchant/settings | Bearer + X-API-Key + confirmApiKey | ตั้งค่า/เปลี่ยนบัญชีธนาคาร |
| POST | /api/merchant/rotate-api-key | X-API-Key | หมุน API key |
| GET | /api/merchant/webhook-logs | X-API-Key | ประวัติส่ง webhook |
| GET | /api/merchant/reconciliation-report | X-API-Key | รายงานกระทบยอด (CSV) |
| HTTP | ความหมาย | ทางแก้ |
|---|---|---|
| 401 | key/token ผิดหรือหมดอายุ | ตรวจ X-API-Key / login ใหม่ |
| 403 | IP ไม่อยู่ใน allowlist, uuid ไม่ตรง, หรือเกินลิมิตเกรดผู้ใช้ | ตรวจ allowedIps / uuid / วงเงินเกรด |
| 409 | idempotencyKey ซ้ำแต่ยอดไม่ตรง | ใช้ key ใหม่ต่อหนึ่งรายการเสมอ |
| 429 | ยิงถี่เกิน (60 req/นาที/IP) | เว้นจังหวะ / กระจายเวลา |
Insufficient Gas/Credit Balance | วงเงินร้านไม่พอ | ติดต่อผู้ดูแลเติมวงเงิน |