← ภาพรวมบัญชี · ฝาก–ถอนให้ลูกค้า · เชื่อม LINE OA
P2P PAYMENTSสำหรับลูกค้า ร้านค้า และเอเยนต์ เลือกเรื่องที่กำลังใช้งาน แล้วกลับไปตรวจรายการจริงในบัญชีของคุณ
ร้านใหม่ต้องตั้งค่าบัญชีรับเงินและหลักประกันให้พร้อมก่อนรับรายการ การใช้ผ่าน LINE ต้องตั้งค่าและเปิด LINE OA ของร้านแยกต่างหาก ส่วนคู่มือ API สำหรับเชื่อมระบบอยู่ด้านล่าง
เริ่มที่ ภาพรวม → ความพร้อมของร้าน ตรวจ GAS บัญชีรับเงิน และ LINE OA ให้พร้อม เจ้าของร้านและผู้ทดสอบเป็นผู้โอนเงินจริงในแอปธนาคารเอง เก็บเลขคำขอไว้ตรวจผลให้จบก่อนเริ่มรายการถัดไป
ถ้าทดสอบจับคู่ข้ามร้าน ให้ใช้สองร้านที่พร้อมและลูกค้าทดสอบของแต่ละร้าน ห้ามแก้ผู้รับในรายการที่สร้างแล้ว ระบบเป็นผู้จับคู่ตามนโยบายเครือข่าย



ภาพชุดนี้อธิบายกติกาของรายการใหม่บนเว็บและ LINE ยอด GAS เป็นยอดใช้งานในระบบ การเติมหรือการใช้ GAS ไม่ใช่หลักฐานรับเงินในธนาคาร และยอดคงเหลือไม่ใช่ยอดค้างชำระ



ครบเวลาจับคู่ไม่ได้แปลว่าเงินเข้าบัญชีแล้ว ร้านต้องตรวจส่วนที่รอจ่าย การโอนอัตโนมัติต้องมีระบบร้านค้าที่เชื่อมต่อและเปิดโหมดนั้นไว้ หน้าเว็บและ LINE ของ P2P ไม่ได้เปิดเพย์เมนต์อัตโนมัติให้ทุกร้าน




ระบบจับคู่ "ลูกค้าที่กำลังฝาก" กับ "ลูกค้าที่กำลังถอน" ให้โอนเงินถึงกันโดยตรง (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 ที่เราส่งไปหาร้าน |
ส่ง x-sandbox: true ตอนสร้างรายการฝาก/ถอนทดสอบ แล้วใช้ ID ของรายการทดสอบนั้นต่อ
header นี้ไม่ใช่โหมดปลอดภัยสำหรับทุก endpoint และห้ามใช้กับ ID ของรายการเงินจริง
# 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) เปิดในเบราว์เซอร์เพื่อดูบัญชีปลายทางและแนบสลิป
# {{BASE}}/payment.html?uuid=<uuid>
# API อ่านข้อมูล JSON: GET /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>" -H "X-API-Key: $API_KEY"
การเติมโดยเจ้าของเป็นการเพิ่มวงเงินกระเป๋าเอเยนต์ตามยอดที่ยืนยัน ไม่ใช่การโอนจากธนาคารอัตโนมัติ ดูประวัติการเติมได้จากบัญชีเอเยนต์ และบันทึกการทำงานของเจ้าของ
ผู้ดูแลระบบจะเติม 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 "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $API_KEY" \
-d '{ "expiresHours": 24 }'
# ได้ key ใหม่ทันที; key เก่าใช้ต่อได้ตามชั่วโมงที่กำหนด (transition period)
สำหรับร้านที่ตั้ง webhookUrl เมื่อรายการเปลี่ยนสถานะ เรา POST ไปที่ URL นั้น ร้านที่เว้นว่างดูสถานะผ่านเว็บ หรือผ่าน LINE เมื่อเชื่อมต่อและเปิดใช้งานแล้ว:
{
"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; },
}), async (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();
}
try {
// interface ตัวอย่าง: ใช้ฐานข้อมูล/คิวถาวรของร้าน และ dedupe งานซ้ำ
await durableInbox.accept(req.body);
return res.status(200).end(); // ตอบหลัง commit งานแล้วเท่านั้น
} catch {
return res.status(503).end(); // ให้ gateway ส่งซ้ำเมื่อบันทึกไม่ได้
}
});
durableInbox เป็น interface ที่ร้านต้องทำด้วยที่เก็บงานถาวรและ worker ของร้าน
ห้ามใช้คิวในหน่วยความจำหรือสั่งงานแล้วตอบก่อนบันทึก การเติม/คืนเครดิตต้อง dedupe พร้อม ledger
ใน transaction เดียวกัน และตรวจร้าน ลูกค้า ประเภท ยอดเงินและรหัสรายการให้ตรง
แนะนำตรวจ X-Webhook-Signature-V2 ด้วย HMAC-SHA256 ของ
v1\n<timestamp>\n<eventId>\n<sequence>\n<rawBody>
จาก headers X-Webhook-Timestamp (Unix seconds), X-Webhook-Event-Id
และ X-Webhook-Sequence จำกัดอายุ timestamp และบันทึก event ID/sequence เพื่อกันซ้ำ
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: สร้างรายการ → ระบบจับคู่กับคนถอน (ปกติภายในวินาที) →
ร้านพาลูกค้าไปหน้า /payment.html?uuid=<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เมื่อครบเวลารอจับคู่ ระบบบันทึกยอดส่วนที่ร้านต้องจ่าย และส่ง webhook
payout.required ไปยังช่องทางที่ร้านเชื่อมไว้ พร้อมบัญชีผู้รับและยอดส่วนนี้
ตัวอย่างถอน 1,000 บาท หาก P2P จ่ายยืนยันแล้ว 600 บาท ร้านจ่ายต่อ 400 บาท:
{
"event": "payout.required",
"data": {
"id": 456, "payoutRef": "b1f0-…", "payoutAmount": "300.0000",
"bankName": "KBank", "bankAccountNo": "123-4-56789-0",
"bankAccountName": "Somchai J."
}
}
ก่อนเปิด auto-payout ต้องทดสอบการส่งผลตรวจสลิปที่ gateway อ่านตรวจซ้ำได้ให้ครบ เมื่อโอนแล้วต้องตรวจสลิปและส่ง signed proof กลับ:
curl --fail-with-body -X POST "{{BASE}}/api/transaction/456/payout-confirm" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "payoutRef": "b1f0-…", "bankRef": "<เลขอ้างอิงธนาคาร>",
"verificationId": "<UUID ผลตรวจสลิป>",
"verificationExpiresAt": 1790000000000,
"verificationSignature": "<HMAC-SHA256 hex>" }'
payoutAmount เสมอ (อาจเป็นแค่เศษที่เหลือ ไม่ใช่ยอดเต็ม)payoutRef = idempotency key — dedupe ฝั่งร้านด้วยค่านี้ ยืนยันซ้ำไม่มีผลข้างเคียงbankRef ไม่ได้: API ร้านค้าจะตอบ 400 หากไม่มีหลักฐานตรวจสลิปverificationExpiresAt ใช้ Unix milliseconds ที่ยังไม่หมดอายุและไม่เกิน 5 นาทีข้างหน้าpayoutStatus: CONFIRMED หากยืนยันไม่ผ่านให้ใช้หลักฐานเดิม
ลงลายเซ็นใหม่แล้ว retry การยืนยัน ห้ามโอนเงินจริงซ้ำเพราะคำตอบหายautoPayoutEnabled การโอนอัตโนมัติขึ้นกับระบบที่ร้านเชื่อมต่อและเปิดใช้เพย์เมนต์ไม่มีรูปสลิป: API ร้านค้าข้างต้นยังต้องมีผลตรวจสลิปที่ตรวจสอบได้ การใช้ผลจ่ายสำเร็จจากเพย์เมนต์ต้องเป็นเส้นทางเชื่อมต่อที่ระบบรองรับโดยเฉพาะ เช่น One2Pays ผ่านระบบร้านค้า Lottery Hub ไม่ได้เปิดให้อัตโนมัติสำหรับร้าน WEB/LINE ทุกแห่ง หากหน้าจอขึ้น “บันทึกยืนยันแล้ว” แต่หลักฐานยังไม่ครบ ให้ผู้ดูแลตรวจรายการเดิมและผลจากระบบที่โอน อย่าโอนซ้ำ
สร้าง verificationSignature ฝั่งเซิร์ฟเวอร์ด้วย HMAC-SHA256 และ webhook secret
ของร้าน บนข้อความที่คั่นด้วย newline ตามลำดับด้านล่าง จำนวนเงินต้องใช้ decimal ทศนิยม 4 ตำแหน่ง
gateway จะตรวจลายเซ็น ดึงผลตรวจจากผู้ให้บริการ และตรวจยอด/ผู้รับ/การใช้สลิปซ้ำอีกครั้ง:
payout-proof-v1
<verificationId.trim()>
<merchantId>
<transactionId>
<payoutRef>
<payoutAmount เช่น 300.0000>
<verificationExpiresAt>
ก่อนร้านจะประมวลผลด้วยระบบตัวเอง ถามเราหนึ่งครั้ง:
# ฝั่งถอน: มีคนฝากรออยู่พอดีไหม?
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 ใน HTTP 2xx ที่ถูกต้อง → ไม่มีรายการถูกสร้าง ร้านใช้ระบบเดิมได้
ห้ามเปลี่ยนช่องทางเมื่อผลยังไม่แน่นอน: timeout, error, คำตอบว่าง/ไม่ครบ หรือ HTTP 409
ไม่ได้แปลว่าไม่มีคู่ ให้เก็บรายการเดิมและ retry ด้วย key/ข้อมูลเดิม
รหัส TRY_MATCH_ALREADY_ACCEPTED หมายถึงเคยรับรายการแล้ว ใช้ transactionId
อ่าน GET /api/transaction/:id ด้วย API key ร้านเจ้าของรายการและตรวจสถานะก่อนดำเนินการต่อ
ร้านต้องล็อกและบันทึกช่องทางจ่ายของแต่ละรายการแบบถาวร เมื่อเลือกจ่ายเองแล้วให้หยุดเรียก try-match ของรายการนั้น ใช้ bank idempotency key เดิมเมื่อกู้รายการ ห้ามจับคู่พร้อมกับโอนเอง เพราะคำขอที่ได้ no-match ไม่ได้ห้ามคำขอซ้ำจับคู่ได้ในภายหลัง
# ฝั่งฝาก: มีคนถอนรอรับพอดีไหม?
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 และข้อมูลเดิมได้เฉพาะก่อนเลือกช่องทางของร้านเอง
เมื่อเลือกช่องทางของร้านแล้ว ต้องหยุด try-match ของรายการนั้นและติดตามผลผ่านช่องทางที่เลือกไว้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 | — | ข้อมูลรายการแบบ JSON · หน้าลูกค้าใช้ /payment.html?uuid=… |
| 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 | X-API-Key ของร้านเจ้าของรายการ | เช็คสถานะรายการ |
| POST | /api/auth/login | name+key | รับ session token จัดการตั้งค่า |
| PATCH | /api/merchant/settings | Bearer + X-API-Key + confirmApiKey | ตั้งค่า/เปลี่ยนบัญชีธนาคาร |
| POST | /api/merchant/rotate-api-key | Console session + X-API-Key | หมุน API key |
| GET | /api/merchant/webhook-logs | Console session + X-API-Key | ประวัติส่ง webhook |
| GET | /api/merchant/reconciliation-report | Console session + X-API-Key | รายงานกระทบยอด |
| HTTP | ความหมาย | ทางแก้ |
|---|---|---|
| 401 | key/token ผิดหรือหมดอายุ | ตรวจ X-API-Key / login ใหม่ |
| 403 | IP ไม่อยู่ใน allowlist, uuid ไม่ตรง, หรือเกินลิมิตเกรดผู้ใช้ | ตรวจ allowedIps / uuid / วงเงินเกรด |
| 409 | key/body ขัดแย้ง หรือ TRY_MATCH_ALREADY_ACCEPTED | ตรวจรายการเดิม ห้ามเปลี่ยน key หรือจ่ายช่องทางอื่นเพื่อหลบ error ใช้ transactionId และ API key ร้านอ่านสถานะ |
| 429 | ยิงถี่เกิน (60 req/นาที/IP) | เว้นจังหวะ / กระจายเวลา |
Insufficient Gas/Credit Balance | วงเงินร้านไม่พอ | ติดต่อผู้ดูแลเติมวงเงิน |