← ภาพรวมบัญชี · ฝาก–ถอนให้ลูกค้า · เชื่อม LINE OA

P2P PAYMENTS

เข้าใจ P2P จากภาพและขั้นตอนสั้น ๆ

สำหรับลูกค้า ร้านค้า และเอเยนต์ เลือกเรื่องที่กำลังใช้งาน แล้วกลับไปตรวจรายการจริงในบัญชีของคุณ

เริ่มใช้งานผ่านหน้าเว็บ

  1. เจ้าของ: เปิด เอเยนต์และเครือข่าย → สร้างบัญชีเอเยนต์ กำหนดรหัสเองหรือเว้นว่างให้ระบบสร้าง แล้วคัดลอกชื่อผู้ใช้และรหัสผ่านจากหน้าต่างยืนยันก่อนปิด
  2. เอเยนต์สร้างเอเยนต์ย่อย: เปิด เอเยนต์ในสาย → สร้างเอเยนต์ย่อย กำหนดชื่อผู้ใช้และอัตราฝาก–ถอนที่ไม่ต่ำกว่าอัตราของตัวเอง กำหนดรหัสผ่านหรือเว้นว่างให้ระบบสร้าง บัญชีใหม่อยู่ใต้เอเยนต์ที่เข้าสู่ระบบและเริ่มด้วยกระเป๋า 0 คัดลอกข้อมูลเข้าระบบจากหน้าต่างยืนยันก่อนปิด
  3. เอเยนต์: เปิด ร้านค้าของฉัน → เพิ่มร้านค้าในสาย ใส่ชื่อร้านและอัตราฝาก–ถอน ใช้ปุ่ม สร้างรหัสผ่านให้ หรือกำหนดรหัส 32–128 ตัวอักษร รหัสนี้เป็นทั้งรหัสเข้าเว็บและ API Key ของร้าน คัดลอกข้อมูลหลังสร้างสำเร็จ ช่อง Webhook เว้นว่างได้ ถ้าใช้ผ่านเว็บนี้หรือ LINE กรอกเฉพาะร้านที่มีระบบภายนอกรับแจ้งสถานะ
  4. ร้านค้า: เข้าด้วยรหัสร้านค้าและรหัสผ่าน เปิด ฝาก–ถอน / อนุมัติคำขอ ตั้งบัญชีรับเงินของร้านก่อน แล้วระบุลูกค้าและจำนวนเงินเพื่อสร้างรายการ
  5. ฝากเงิน: เปิด ดูรายละเอียด → เปิดหน้าชำระเงิน / แนบสลิป หรือคัดลอกลิงก์ให้ลูกค้า เมื่อลูกค้าโอนแล้วให้แนบสลิปของรายการนั้น ระบบจะแสดงยืนยันฝากเมื่อมีผลตรวจผ่าน
  6. ถอนเงิน: ตรวจบัญชีและอนุมัติคำขอจากลูกค้าก่อนเข้าคิว เมื่อครบเวลาจับคู่ ถ้ามีส่วนที่ร้านต้องจ่าย รายละเอียดจะแสดงจำนวนและบัญชีให้ตรวจ ร้านโอนส่วนนี้เองแล้วแนบสลิป ระบบตรวจหลักฐานก่อนยืนยัน
  7. ถ้าการตรวจช้าหรือคำตอบขาดหาย: ใช้ ตรวจสถานะหลังส่งสลิป หรือ ตรวจผลสลิปเดิม อย่าสร้างรายการใหม่หรือโอนซ้ำ สามารถเปิดหลักฐานที่ยืนยันแล้วจากรายละเอียดรายการ

ร้านใหม่ต้องตั้งค่าบัญชีรับเงินและหลักประกันให้พร้อมก่อนรับรายการ การใช้ผ่าน LINE ต้องตั้งค่าและเปิด LINE OA ของร้านแยกต่างหาก ส่วนคู่มือ API สำหรับเชื่อมระบบอยู่ด้านล่าง

ทดสอบฝาก–ถอนผ่าน LINE ทีละรายการ

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

  1. เตรียมร้าน: เปิด บัญชีรับเงินร้าน ตรวจบัญชีที่เปิดใช้และเพดานสะสมรายวัน จากนั้นเปิด เชื่อม LINE OA ตรวจทุกจุดให้พร้อมก่อนเปิดใช้งาน
  2. ฝาก: ถ้าเลือกร้านสร้างรายการให้ ร้านเปิดหน้าฝาก–ถอน เลือกลูกค้าและยอด แล้วยืนยัน ระบบแจ้งผ่าน LINE ให้ลูกค้าเปิดบัญชีรับเงินและส่งสลิป ถ้าเปิดให้ลูกค้าสร้างเองจึงใช้คำสั่ง ฝาก 20 ได้ ตรวจชื่อผู้รับและเลขบัญชีจากรายการล่าสุด โอนหนึ่งครั้งและส่งสลิปของรายการนั้น
  3. ตรวจผลฝาก: ต้องมีผลตรวจสลิปผ่านและรายการยืนยันแล้ว ตรวจเลขคำขอ ยอด และหลักฐานในหน้าเว็บร้าน หากยังรอตรวจ ให้ตรวจสถานะรายการเดิมและรอผล ห้ามตีความว่าส่งรูปแล้วเท่ากับอนุมัติ
  4. ทดสอบสลิปซ้ำ: ส่งสลิปเดิมในคำขอเดิมอีกครั้ง ไม่โอนเงินเพิ่ม ผลที่ถูกต้องคือไม่เกิดรายการฝากหรือการบันทึกยอดซ้ำ ตรวจเลขรายการและบันทึกบัญชีเดิมประกอบ
  5. ถอน: ให้ลูกค้าบันทึกบัญชีรับเงินของตัวเอง ร้านเปิด ฝาก–ถอน / อนุมัติคำขอ เลือกลูกค้า ยอด และบัญชี ตรวจแล้วจึงยืนยันเข้าคิว ส่วนคำสั่ง ถอน 20 ใช้ได้เฉพาะร้านที่เปิดให้ลูกค้าสร้างคำขอเอง และร้านยังต้องอนุมัติก่อนเข้าคิว
  6. ส่วนที่ร้านต้องโอน: รอเวลาจับคู่ตามที่ตั้งไว้ ถ้ามีส่วนขาด ให้ดูยอดที่ระบบระบุในรายละเอียด โอนเฉพาะส่วนนั้นและแนบสลิป รอผลตรวจผ่านก่อนถือว่าจ่ายครบ ไม่ใช้ยอดถอนรวมแทนยอดส่วนขาด
  7. ปิดการทดสอบ: ตรวจว่าผู้รับได้รับเงินจริงครบและเปิดหลักฐานได้ ตรวจประวัติฝั่งลูกค้า–ร้านและรายงานช่วงเวลาเดียวกัน รายงานบัญชีที่เท่ากันอย่างเดียวไม่ยืนยันการรับเงินในธนาคาร

ถ้าทดสอบจับคู่ข้ามร้าน ให้ใช้สองร้านที่พร้อมและลูกค้าทดสอบของแต่ละร้าน ห้ามแก้ผู้รับในรายการที่สร้างแล้ว ระบบเป็นผู้จับคู่ตามนโยบายเครือข่าย

เริ่มใช้งานและดูแลสายงาน

GAS ใช้ทำอะไร และรายได้มาจากไหน

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

ถอนยังไม่ครบ ใครจ่าย และตรวจหลักฐานอย่างไร

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

ติดตามสถานะและแจ้งปัญหา

คู่มือ API สำหรับทีมพัฒนาและการเชื่อมต่อระบบ

1. ระบบนี้ทำอะไรให้ร้านค้า

ระบบจับคู่ "ลูกค้าที่กำลังฝาก" กับ "ลูกค้าที่กำลังถอน" ให้โอนเงินถึงกันโดยตรง (peer-to-peer) พร้อม escrow และตรวจสลิปอัตโนมัติ ผลคือ:

การเชื่อมต่อ API เดิมมี 2 แบบ (ไม่ใช่กติกาคำขอร้านค้าใหม่)

Mode B — เต็มรูปแบบMode A — เสริมระบบเดิม (overlay)
เหมาะกับร้านที่อยากให้เราดูแลทุกรายการร้านที่มีระบบฝาก–ถอนออโต้อยู่แล้ว
วิธีทำงานส่งทุกรายการเข้าระบบเรา เราจับคู่และดูแลจนจบถามเราก่อนว่า "ตอนนี้มีคู่ไหม?" มีคู่ = ยกให้เรา ไม่มี = ใช้ระบบร้านตามเดิม
ไม่มีคู่ร้านรับ/จ่ายแทนอัตโนมัติ (ข้อ 8)ไม่มีอะไรเกิดขึ้นในระบบเราเลย
Endpoint หลักPOST /api/deposit, POST /api/withdrawPOST /api/deposit/try, POST /api/withdraw/try

2. สิ่งที่จะได้รับจากผู้ดูแลระบบ

สิ่งที่ได้รับใช้ทำอะไร
BASE_URLที่อยู่ API — สำหรับระบบนี้คือ (โหลดอัตโนมัติ)
ชื่อร้าน (merchant name)ใช้เป็น username ตอน login จัดการตั้งค่า
API_KEYใส่ header X-API-Key ทุกครั้งที่ยิง API (ความลับสูงสุด)
WEBHOOK_SECRETใช้ตรวจลายเซ็น webhook ที่เราส่งไปหาร้าน
เก็บ API key ใน secret manager ของร้าน ห้าม hardcode ในโค้ดหรือ commit ลง git

3. เริ่มใช้ใน 5 นาที (โหมด sandbox — ไม่แตะเงินจริง)

ส่ง 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"
ถ้าทั้ง 4 ขั้นตอบตามคาด แปลว่าทางเชื่อม sandbox ใช้ได้ ยังต้องตรวจ webhook การกันซ้ำ ผลตรวจสลิป และการกระทบยอดครบวงก่อนเปิดรับเงินจริง

4. เข้าใจ Gas และ Credit (สำคัญ — อ่านก่อนใช้เงินจริง)

เจ้าของเติมให้เอเยนต์ และเอเยนต์เติม Gas ให้ร้านค้า

  1. เจ้าของ: เปิด เครือข่าย → เอเยนต์และเครือข่าย ที่หัวข้อ เติมเงินให้เอเยนต์ เลือกเอเยนต์ กรอกจำนวน PTS และหมายเหตุ ตรวจชื่อผู้รับกับยอดก่อน–หลัง แล้วกดยืนยันได้เลย ไม่ต้องรอเอเยนต์ส่งคำขอ
  2. เอเยนต์: เปิด เครือข่าย → ร้านค้าของฉัน → เติม Gas เลือกร้านในสายและจำนวน ระบบหักกระเป๋าเอเยนต์เท่ากับ Gas ที่เพิ่มให้ร้าน ต้องมียอดกระเป๋าเพียงพอ
  3. ถ้าหน้าเว็บยังไม่ได้รับคำตอบ ให้กด ตรวจรายการเติมเดิม / ลองอีกครั้ง ระบบใช้เลขรายการเดิมเพื่อไม่เพิ่มยอดซ้ำ แม้รีโหลดหน้าแล้ว อย่าสร้างรายการใหม่จากแท็บอื่นจนกว่าจะตรวจประวัติเดิม

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

ผู้ดูแลระบบจะเติม gas/credit เริ่มต้นให้ตามที่ตกลง — ถ้าเจอ Insufficient Gas Balance / Insufficient Credit Balance แปลว่าต้องเติมวงเงินก่อน

ลิมิตต่อผู้ใช้: ระบบจัดเกรดลูกค้าอัตโนมัติ (NEW → C → B → A) ลูกค้าใหม่เริ่มที่ 1,000/รายการ ขยับขึ้นตามประวัติที่ดี สูงสุดเกรด A ที่ 50,000/รายการ — ป้องกันบัญชีม้าโดยร้านไม่ต้องทำอะไรเพิ่ม

5. จัดการตั้งค่าเอง (รวมเปลี่ยนบัญชีธนาคารของร้าน)

5.1 login รับ session token

curl -X POST "{{BASE}}/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{ "username": "<ชื่อร้าน>", "password": "<API_KEY>" }'
# ตอบกลับ: { "token": "…", "role": "merchant", … }   (อายุ 24 ชม.)

5.2 แก้ไขตั้งค่า / เปลี่ยนบัญชีธนาคาร

ต้องส่งครบ 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

บัญชีธนาคารของร้านถูกใช้เมื่อ (ก) deposit ไม่มีคู่ → ลูกค้าโอนเข้าบัญชีนี้ และ (ข) เป็นบัญชีต้นทางเวลาระบบออโต้ของร้านจ่ายถอนแทน (ข้อ 8)

5.3 หมุน API key (แนะนำทำเป็นระยะ)

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)

6. Webhook — ระบบแจ้งผลกลับหาร้านแบบ real-time

สำหรับร้านที่ตั้ง 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 พาลูกค้าไปหน้ารายการได้โดยตรง

6.1 ตรวจลายเซ็น (จำเป็น — ห้ามเชื่อ webhook ที่ไม่ผ่านการตรวจ)

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 เพื่อกันซ้ำ

7. Mode B — เชื่อมเต็มรูปแบบ

7.1 ฝากเงิน (ลูกค้าเติมเครดิต)

POST /api/deposit   (X-API-Key)
body: { merchantUserId, amount, idempotencyKey, redirectUrl?, slipUrl? }

Flow: สร้างรายการ → ระบบจับคู่กับคนถอน (ปกติภายในวินาที) → ร้านพาลูกค้าไปหน้า /payment.html?uuid=<uuid> (มีเลขบัญชีปลายทาง + ปุ่มแนบสลิป) → ลูกค้าโอน + กด "โอนแล้ว" → ระบบตรวจสลิป/รอผู้ถอนยืนยัน → webhook completed → ร้านเติมเครดิตให้ลูกค้าในระบบร้าน

7.2 ถอนเงิน (ลูกค้าเอาเงินออก)

POST /api/withdraw   (X-API-Key)
body: { merchantUserId, amount, idempotencyKey,
        bankName, bankAccountNo, bankAccountName }   ← บัญชีของลูกค้าผู้รับเงิน

Flow: ระบบจับคู่กับคนฝาก → คนฝากโอนเข้าบัญชีลูกค้าของร้านโดยตรง → สลิปผ่าน/ผู้ถอนกดยืนยัน/ครบหน้าต่างโต้แย้ง → webhook completed

7.3 การยืนยันรายการและหลักฐานการโอน

  1. ยืนยันรับเงิน — POST /api/transaction/:id/release ต้องผ่านเงื่อนไขหลักฐานของรายการ การกดยืนยันไม่ใช่ทางข้ามการตรวจสลิป
  2. สลิปตรวจผ่าน — ระบบตรวจยอด ผู้รับ และการใช้ซ้ำก่อนชำระบัญชีตามสถานะรายการ
  3. ครบหน้าต่างโต้แย้ง — ตัวทำงานอัตโนมัติยังต้องผ่านด่านหลักฐาน เวลาอย่างเดียวไม่ยืนยันว่าได้รับเงินจริง มีปัญหาให้ใช้ POST /api/transaction/:id/dispute

8. จ่ายยอดที่เหลือ — ร้านโอนเองหรือใช้ระบบอัตโนมัติ

เมื่อครบเวลารอจับคู่ ระบบบันทึกยอดส่วนที่ร้านต้องจ่าย และส่ง 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>" }'

เพย์เมนต์ไม่มีรูปสลิป: 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>

9. Mode A — เสริมระบบเดิม (try-match)

ก่อนร้านจะประมวลผลด้วยระบบตัวเอง ถามเราหนึ่งครั้ง:

# ฝั่งถอน: มีคนฝากรออยู่พอดีไหม?
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 → ให้ลูกค้าโอนเข้าบัญชีร้านตามระบบเดิม
เมื่อได้ HTTP 2xx พร้อม matched:false ที่ถูกต้อง คำขอนั้นไม่ได้สร้างรายการฝาก–ถอน ลองใหม่ด้วย idempotencyKey และข้อมูลเดิมได้เฉพาะก่อนเลือกช่องทางของร้านเอง เมื่อเลือกช่องทางของร้านแล้ว ต้องหยุด try-match ของรายการนั้นและติดตามผลผ่านช่องทางที่เลือกไว้
ร้านเปิดใหม่: ช่วงแรกระบบจะจำกัดวงเงิน "จับคู่ข้ามร้าน" อัตโนมัติ (เพดานเพิ่มขึ้นตามอายุและประวัติของร้าน) — ช่วง warm-up จึงอาจได้ matched:false บ่อยกว่าปกติ ซึ่งไม่ใช่ความผิดพลาด: ร้านใช้ระบบเดิมของตัวเองไปตามปกติ ส่วนการจับคู่ภายในร้านเดียวกันใช้ได้เต็มที่ตั้งแต่วันแรก

10. ตัวเลือกความปลอดภัยเพิ่มเติม (แนะนำเปิดเมื่อขึ้นจริง)

ตัวเลือกวิธีเปิดผล
IP allowlistallowedIps: "1.2.3.4, 10.0.0.0/24" ใน settingsยิง API ได้เฉพาะจาก IP ร้าน
Webhook encryptionwebhookEncryptionEnabled: truepayload เข้ารหัส AES-256-GCM
Key rotationPOST /api/merchant/rotate-api-keyเปลี่ยน key โดยไม่มี downtime

11. Checklist ก่อนเปิดใช้จริง

12. อ้างอิงย่อ

สถานะรายการ (lifecycle)

PENDING_MATCH → WAITING_PAYMENT → PAID_UNCONFIRMED → COMPLETED │ │ └→ DISPUTED → (COMPLETED | CANCELLED) │ └→ CANCELLED └→ GAS_SETTLED (ปิดผ่านบัญชีร้าน/สลิป instant)

Endpoint ทั้งหมดที่ร้านใช้

MethodPathAuthใช้ทำอะไร
POST/api/depositX-API-Keyสร้างรายการฝาก
POST/api/withdrawX-API-Keyสร้างรายการถอน
POST/api/deposit/tryX-API-Keyถามจับคู่ฝากแบบทันที (Mode A)
POST/api/withdraw/tryX-API-Keyถามจับคู่ถอนแบบทันที (Mode A)
GET/api/checkout/:idOrUuid—ข้อมูลรายการแบบ JSON · หน้าลูกค้าใช้ /payment.html?uuid=…
POST/api/transaction/:id/transferreduuid หรือ X-API-Keyแจ้ง "โอนแล้ว" (+สลิป)
POST/api/transaction/:id/releaseX-API-Keyยืนยันรับเงิน/ปล่อยเครดิต
POST/api/transaction/:id/disputeX-API-Keyเปิดข้อโต้แย้ง
POST/api/transaction/:id/payout-confirmX-API-Keyยืนยันจ่ายแทนแล้ว (auto-payout)
GET/api/transaction/:idX-API-Key ของร้านเจ้าของรายการเช็คสถานะรายการ
POST/api/auth/loginname+keyรับ session token จัดการตั้งค่า
PATCH/api/merchant/settingsBearer + X-API-Key + confirmApiKeyตั้งค่า/เปลี่ยนบัญชีธนาคาร
POST/api/merchant/rotate-api-keyConsole session + X-API-Keyหมุน API key
GET/api/merchant/webhook-logsConsole session + X-API-Keyประวัติส่ง webhook
GET/api/merchant/reconciliation-reportConsole session + X-API-Keyรายงานกระทบยอด

Error ที่พบบ่อย

HTTPความหมายทางแก้
401key/token ผิดหรือหมดอายุตรวจ X-API-Key / login ใหม่
403IP ไม่อยู่ใน allowlist, uuid ไม่ตรง, หรือเกินลิมิตเกรดผู้ใช้ตรวจ allowedIps / uuid / วงเงินเกรด
409key/body ขัดแย้ง หรือ TRY_MATCH_ALREADY_ACCEPTEDตรวจรายการเดิม ห้ามเปลี่ยน key หรือจ่ายช่องทางอื่นเพื่อหลบ error ใช้ transactionId และ API key ร้านอ่านสถานะ
429ยิงถี่เกิน (60 req/นาที/IP)เว้นจังหวะ / กระจายเวลา
Insufficient Gas/Credit Balanceวงเงินร้านไม่พอติดต่อผู้ดูแลเติมวงเงิน