ภาพรวม
ภาพรวมข้อมูลของบัญชีและรายการที่ต้องติดตาม
ยอดคงเหลือของร้านค้า
| ร้านค้า | ยอดชำระบัญชี (PTS) |
|---|
เอเยนต์ในเครือข่าย
ประวัติฝาก–ถอน
| เลขรายการ | ร้านค้า | ลูกค้า / ระดับ | ประเภท | จำนวน | สถานะ | รายการที่จับคู่ | เวลาคงเหลือ | รายละเอียด |
|---|
เลือกช่วงรายงาน
ยึดเวลาบันทึกบัญชีในช่วงที่เลือก (เวลาไทย) แยกยอดเติม GAS ค่าธรรมเนียม และยอดพักแล้ว ยอดถอนคือภาระที่บันทึก อาจยังรอร้านโอนเงินจริง รายงานนี้ใช้ยืนยันว่าเงินหายหรือเข้าบัญชีธนาคารแล้วไม่ได้ ต้องตรวจสลิปและสถานะรายการประกอบ
ความเคลื่อนไหวและหลักฐาน
กดตรวจรายงานเพื่อโหลดข้อมูล
ยอดฝาก–ถอนร้านค้า ที่ตรวจหลักฐานผ่านแล้ว
ฝากสำเร็จ (บาท)
ถอนสำเร็จ (บาท)
ฝากหักถอน (บาท)
ตามเวลายืนยันหลักฐานของรายการรูปแบบใหม่ รวมที่ร้านรับหรือจ่ายเอง ยอดนี้แยกจาก GAS และเครดิตการเชื่อมต่อเดิม
| วันเวลา (ไทย) | รายการ / หลักฐาน | บัญชี | เปลี่ยนแปลง | ยอดหลังบันทึก |
|---|---|---|---|---|
| ยังไม่ได้โหลดรายงาน | ||||
ยอดและสถานะรายการ
ยอดรายการผ่าน P2P และบัญชีร้านค้า
สัดส่วนสถานะรายการ
หลักประกันและยอดร้านค้า
บันทึกความปลอดภัย
| เลขบันทึก | ประเภทผู้ทำรายการ | รหัสผู้ทำรายการ | ผู้ทำรายการ | การดำเนินการ | IP Address | วันเวลา | รายละเอียด |
|---|---|---|---|---|---|---|---|
| ยังไม่มีบันทึกตรวจสอบ | |||||||
กิจกรรมบัญชีร้านค้า
| เลขบันทึก | การดำเนินการ | IP Address | วันเวลา | รายละเอียด |
|---|---|---|---|---|
| ยังไม่มีประวัติการใช้งาน | ||||
ทดสอบการเชื่อมต่อ API
ผลการเรียก API
รายการที่ต้องตรวจสอบ
0 ACTIVEคำขออนุมัติการตรวจสอบ
0 PENDINGประวัติการตัดสินโดยผู้ดูแล
| เลขรายการ | ผู้ดูแล | การดำเนินการ | เหตุผล | วันเวลา |
|---|
หัวข้อคู่มือ API
1. การยืนยันตัวตน
All partner API requests sent to the P2P gateway must include your merchant specific API Key in the HTTP headers. Keep this key secure and do not share it.
X-API-Key: key-lucky-games
2. คำขอฝากเงิน
Creates a deposit order. The gateway immediately locks the requested amount of gas from your gas_balance to cover potential netting fallbacks.
{
"merchantUserId": "user_a1",
"amount": "100.0000",
"idempotencyKey": "ik_1781294819"
}
{
"id": 142,
"merchantId": 1,
"userId": 1,
"type": "DEPOSIT",
"amount": "100.0000",
"status": "PENDING_MATCH",
"idempotencyKey": "ik_1781294819",
"createdAt": "2026-06-13T10:00:00.000Z"
}
3. คำขอถอนเงิน
Creates a withdrawal order. No gas is reserved. Withdrawals are queued and matched against deposit orders of the same amount.
{
"merchantUserId": "user_b1",
"amount": "100.0000",
"idempotencyKey": "ik_901847192"
}
4. การยืนยันรายการ P2P
Once orders are matched, customers interact directly to transfer cash. Partners signal transaction status updates to the gateway.
A. Mark Transferred (Paid)
The depositor signals that cash has been transferred to the withdrawer's bank account.
B. Confirm & Release
The withdrawer verifies receipt of the funds and releases the escrow credit balance.
C. File Dispute
If funds do not arrive, the withdrawer opens a dispute, pausing timeout expiration.
{
"reason": "Customer uploaded fake slip"
}
5. รูปแบบข้อมูลแจ้งสถานะ Webhook
The gateway posts webhook updates to your registered webhookUrl when transaction states transition.
{
"event": "transaction.updated",
"timestamp": "2026-06-13T10:15:00.000Z",
"data": {
"txId": 142,
"merchantId": 1,
"type": "DEPOSIT",
"amount": "100.0000",
"status": "COMPLETED",
"idempotencyKey": "ik_1781294819"
}
}
5.1 Webhook Signature Verification (การตรวจสอบความถูกต้องลายเซ็น)
เพื่อความปลอดภัยสูงสุด ร้านค้าควรอ่านและตรวจสอบลายเซ็นที่ส่งมาใน Header X-Webhook-Signature ทุกครั้ง เพื่อป้องกันการปลอมแปลงคำขอโอนเงินจากบุคคลภายนอก ลายเซ็นนี้คำนวณจาก **HMAC-SHA256** โดยใช้ raw body ของ webhook payload ร่วมกับ Webhook Secret ของร้านค้า
const crypto = require('crypto');
app.post('/api/callback', (req, res) => {
const signatureHeader = req.headers['x-webhook-signature'];
const webhookSecret = 'your_whsec_secret_here'; // ดูได้ที่หน้า Settings
// คำนวณ Signature จาก Raw Body
const rawBody = JSON.stringify(req.body);
const calculatedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody)
.digest('hex');
if (calculatedSignature === signatureHeader) {
console.log('Webhook signature valid!');
return res.status(200).send('success');
} else {
console.error('Invalid signature!');
return res.status(401).send('Unauthorized');
}
});
<?php
$signatureHeader = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$webhookSecret = 'your_whsec_secret_here';
// รับ Raw Request Body
$rawBody = file_get_contents('php://input');
$calculatedSignature = hash_hmac('sha256', $rawBody, $webhookSecret);
if (hash_equals($calculatedSignature, $signatureHeader)) {
echo "Webhook signature valid!";
http_response_code(200);
} else {
echo "Invalid signature!";
http_response_code(401);
}
?>
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/api/callback', methods=['POST'])
def webhook_callback():
signature_header = request.headers.get('X-Webhook-Signature')
webhook_secret = b'your_whsec_secret_here'
# คำนวณ Signature จาก Raw Data
raw_body = request.get_data()
calculated_signature = hmac.new(
webhook_secret,
raw_body,
hashlib.sha256
).hexdigest()
if hmac.compare_digest(calculated_signature, signature_header):
print("Webhook signature valid!")
return "success", 200
else:
print("Invalid signature!")
abort(401)
5.2 Webhook Payload Decryption (การถอดรหัสข้อมูล Webhook)
หากร้านค้าเปิดใช้งาน Webhook Payload Encryption ในหน้าการตั้งค่า ข้อมูลดิบ (raw body) ของ Webhook จะถูกเข้ารหัสเพื่อความปลอดภัยระดับสูงโดยใช้ AES-256-GCM ข้อมูลจะจัดส่งมาในรูปแบบ JSON ดังนี้:
{
"ciphertext": "<hex_encoded_encrypted_data>",
"iv": "<hex_encoded_initialization_vector_12_bytes>",
"tag": "<hex_encoded_authentication_tag_16_bytes>"
}
ในการถอดรหัส ร้านค้าต้องใช้ Webhook Secret เพื่อนำมาหาค่าแฮช SHA-256 (32 ไบต์) สำหรับใช้เป็นคีย์สมมาตรหลักในการถอดรหัส AES-256-GCM:
const crypto = require('crypto');
function decryptWebhookPayload(encryptedPayload, webhookSecret) {
const { ciphertext, iv, tag } = encryptedPayload;
// 1. Derive the 32-byte key via SHA-256 hash of the webhook secret
const key = crypto.createHash('sha256').update(webhookSecret).digest();
// 2. Setup decipher using AES-256-GCM
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(tag, 'hex'));
// 3. Decrypt the payload
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
<?php
function decryptWebhookPayload($encryptedPayload, $webhookSecret) {
$ciphertext = hex2bin($encryptedPayload['ciphertext']);
$iv = hex2bin($encryptedPayload['iv']);
$tag = hex2bin($encryptedPayload['tag']);
// 1. Derive key via SHA-256 hash of secret
$key = hash('sha256', $webhookSecret, true);
// 2. Decrypt using openssl
$decrypted = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($decrypted === false) {
throw new Exception('Decryption failed');
}
return json_decode($decrypted, true);
}
?>
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import hashlib
import json
def decrypt_webhook_payload(encrypted_payload, webhook_secret):
ciphertext = bytes.fromhex(encrypted_payload['ciphertext'])
iv = bytes.fromhex(encrypted_payload['iv'])
tag = bytes.fromhex(encrypted_payload['tag'])
# 1. Derive key via SHA-256 hash of secret
key = hashlib.sha256(webhook_secret.encode('utf-8')).digest()
# 2. Decrypt using AESGCM (cryptography expects ciphertext and tag concatenated)
aesgcm = AESGCM(key)
decrypted_bytes = aesgcm.decrypt(iv, ciphertext + tag, None)
return json.loads(decrypted_bytes.decode('utf-8'))
6. คู่มือการเชื่อมต่อ API (API Integration Guide)
คำแนะนำทีละขั้นตอนสำหรับนักพัฒนาในการเชื่อมต่อระบบร้านค้า (Merchant System) เข้ากับเกตเวย์ P2P (Cross-Merchant P2P Gateway)
ขั้นตอนการฝากเงิน (Flow A: Deposit - Cash-In)
POST /api/v1/deposit โดยระบบจะตรวจสอบ Gas Balance ของร้านค้า จากนั้นทำการสำรอง (Reserve) Gas ในอัตรา 1:1 กับยอดฝาก เพื่อการันตีธุรกรรม และปรับสถานะเป็น PENDING_MATCH
WAITING_PAYMENT และส่ง Webhook ไปยัง webhookUrl ของร้านค้า โดยจะมีรายละเอียดบัญชีธนาคารของผู้รับเงินปลายทางแนบไปด้วย
POST /api/v1/transaction/:id/transferred เพื่อปรับสถานะธุรกรรมเป็น PAID_UNCONFIRMED
COMPLETED ค่า Gas ที่ล็อคไว้จะถูก Netting หักล้างและบันทึกดุลบัญชีอย่างเป็นทางการ
ขั้นตอนการถอนเงิน (Flow B: Withdraw - Cash-Out)
POST /api/v1/withdraw โดยระบบจะนำคำขอนี้เข้าสู่ Matching Engine เพื่อรอการจับคู่กับรายการฝาก
PAID_UNCONFIRMED เพื่อส่งสัญญาณให้คุณไปตรวจสอบบัญชีธนาคารจริง
POST /api/transaction/:id/release เพื่อส่งเครดิตและจบธุรกรรม (สถานะ COMPLETED)
POST /api/transaction/:id/dispute ทันที เพื่อระงับเวลาถอยหลัง และส่งธุรกรรมเข้าสู่ Dispute Center ให้แอดมินพิจารณาตัดสิน
ตัวอย่างการเขียนโปรแกรมเชื่อมต่อ (Code Examples)
const axios = require('axios');
async function createDeposit() {
try {
const response = await axios.post('http://localhost:3005/api/v1/deposit', {
merchantUserId: 'user_lucky_01',
amount: '100.00',
idempotencyKey: 'ik_' + Date.now()
}, {
headers: {
'X-API-Key': 'key-lucky-games',
'Content-Type': 'application/json'
}
});
console.log('Order Created:', response.data);
} catch (error) {
console.error('API Error:', error.response ? error.response.data : error.message);
}
}
<?php
$ch = curl_init('http://localhost:3005/api/v1/deposit');
$payload = json_encode([
'merchantUserId' => 'user_lucky_01',
'amount' => '100.00',
'idempotencyKey' => 'ik_' . time()
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Key: key-lucky-games'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo "Response: " . $result;
?>
import requests
import time
url = "http://localhost:3005/api/v1/deposit"
headers = {
"Content-Type": "application/json",
"X-API-Key": "key-lucky-games"
}
payload = {
"merchantUserId": "user_lucky_01",
"amount": "100.00",
"idempotencyKey": f"ik_{int(time.time())}"
}
response = requests.post(url, json=payload, headers=headers)
print("Response JSON:", response.json())
7. การหมุนเวียนคีย์โดยไม่หยุดชะงัก (Zero-Downtime API Key Rotation)
ในการหมุนเวียนคีย์ API (API Key Rotation) เกตเวย์จะเปิดใช้งานคีย์เดิมเป็น Transition Key อัตโนมัติเป็นเวลา 24 ชั่วโมงหลังจากสร้างคีย์ใหม่ ระบบหลังบ้านของร้านค้าสามารถส่งคำขอด้วยคีย์ใหม่ หรือใช้คีย์เดิมในช่วงเปลี่ยนผ่านได้โดยไม่มีช่วงระบบล่ม (Zero Downtime)
const axios = require('axios');
// เก็บคีย์ API ล่าสุด และคีย์สำรอง (Transition Key)
const apiClient = axios.create({
baseURL: 'http://localhost:3005',
timeout: 5000,
headers: { 'Content-Type': 'application/json' }
});
const API_KEYS = {
primary: 'key-rotated-8bf292cb', // คีย์ใหม่ล่าสุด
transition: 'key-lucky-games' // คีย์เก่า (ที่กำลังใช้งานในช่วงเปลี่ยนผ่าน)
};
// Middleware หรือ helper สำหรับยิง HTTP request แบบมี Failover
async function requestWithKeyRotation(endpoint, data) {
// 1. ลองยิงด้วยคีย์ Primary ก่อน
try {
const response = await apiClient.post(endpoint, data, {
headers: { 'X-API-Key': API_KEYS.primary }
});
console.log('Success with Primary Key!');
return response.data;
} catch (error) {
// 2. หากเฟลด้วยสิทธิ์ไม่ถูกต้อง (401 Unauthorized) ให้ลอง fallback ยิงด้วย Transition Key
if (error.response && error.response.status === 401 && API_KEYS.transition) {
console.warn('Primary Key unauthorized. Trying Transition API Key...');
try {
const response = await apiClient.post(endpoint, data, {
headers: { 'X-API-Key': API_KEYS.transition }
});
console.log('Success with Transition Key!');
return response.data;
} catch (fallbackError) {
throw fallbackError;
}
}
throw error;
}
}
// วิธีเรียกใช้งาน
// requestWithKeyRotation('/api/deposit', { ... });
8. การตรวจสอบลายเซ็น Webhook (Webhook Signature Verification)
ในการส่งข้อมูลการอัปเดตสถานะธุรกรรม (Webhook) เกตเวย์จะคำนวณค่าแฮช HMAC-SHA256 ของ Payload ดิบด้วย Webhook Secret ของร้านค้า และส่งมาใน Header X-Webhook-Signature
ร้านค้าต้องนำข้อมูลใน Body ทั้งหมดมาทำแฮชเปรียบเทียบในฝั่งเซิร์ฟเวอร์ปลายทางเพื่อยืนยันว่าข้อมูลส่งมาจากเกตเวย์จริงและไม่ได้ถูกปลอมแปลง (Signature Verification)
const express = require('express');
const crypto = require('crypto');
const app = express();
// หมายเหตุ: ต้องใช้ Raw Body เพื่อคำนวณลายเซ็นที่ถูกต้อง
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const webhookSecret = "your_whsec_secret_here"; // จะแทนที่ด้วยความลับจริงเมื่อเข้าระบบ
const computedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(req.body) // req.body ต้องเป็น Raw Buffer ที่ได้จาก express.raw
.digest('hex');
if (signature === computedSignature) {
console.log('Webhook signature valid!');
const payload = JSON.parse(req.body.toString());
console.log('Processed Event:', payload.event, 'Transaction ID:', payload.data.id);
res.sendStatus(200);
} else {
console.error('Webhook signature verification failed!');
res.sendStatus(400);
}
});
เติมเงินให้เอเยนต์
เจ้าของเติมเข้ากระเป๋าเอเยนต์ได้โดยตรง เอเยนต์นำยอดนี้ไปเติม Gas ให้ร้านค้าในสายได้
สร้างบัญชีในเครือข่าย
รายชื่อในเครือข่าย
ร้านค้า
| รหัส | ชื่อ | ข้อมูลเข้าสู่ระบบ | URL รับการแจ้งสถานะ (Webhook) | เอเยนต์ | อัตราฝาก (%) | อัตราถอน (%) | การดำเนินการ |
|---|
เอเยนต์
| รหัส | ชื่อผู้ใช้ | เอเยนต์ต้นสาย | อัตราฝาก (%) | อัตราถอน (%) | การดำเนินการ |
|---|
สร้างบัญชีผู้ช่วย
รายชื่อผู้ช่วย
| ผู้ช่วย | บทบาท | สิทธิ์ | สถานะ | จัดการ |
|---|
คำขอเติมกระเป๋าเอเยนต์
| เลขคำขอ | เอเยนต์ | ยอดที่ขอ | หลักฐาน | วันที่ส่งคำขอ | จัดการ |
|---|
คำขอถอนกระเป๋าเอเยนต์
| เลขคำขอ | เอเยนต์ | ยอดที่ขอ | บัญชีรับเงิน | วันที่ส่งคำขอ | จัดการ |
|---|
ตรวจความครบถ้วนของบัญชี
ผลการตรวจบัญชี
ตรวจบัญชีร้านค้า
| ร้านค้า | Gas (DB/Ledger) | สถานะยอด Gas | Credit (DB/Ledger) | สถานะเครดิต |
|---|---|---|---|---|
| ยังไม่มีผลตรวจ กดตรวจยอดเพื่อโหลดข้อมูล | ||||
ตรวจบัญชีเอเยนต์
| เอเยนต์ | Wallet (DB/Ledger) | ผลตรวจความสอดคล้อง |
|---|---|---|
| ยังไม่มีผลตรวจ กดตรวจยอดเพื่อโหลดข้อมูล | ||
ประวัติการตรวจบัญชี
| เวลาตรวจ | สถานะ | ผลต่าง | รายละเอียดผลต่าง |
|---|---|---|---|
| ยังไม่มีประวัติการตรวจยอด | |||
สร้างเอเยนต์ย่อย · ระบบเดิม
สายงานเดิม
| รหัส | ชื่อผู้ใช้ | เอเยนต์ต้นสาย | อัตราฝาก (%) | อัตราถอน (%) | ยอดกระเป๋า (PTS) |
|---|
เพิ่มร้านค้าในสาย
ร้านค้าของฉัน
| รหัส | ชื่อ | สถานะ | Gas ที่พักไว้ (PTS) | อัตราฝาก (%) | อัตราถอน (%) | Gas (PTS) | การดำเนินการ |
|---|
โอนระหว่างสายงานเดิม
ประวัติเติม Gas ให้ร้านค้า
| วันเวลา | ร้านค้า | ประเภท | จำนวน (PTS) |
|---|
ข้อมูลร้านค้าและการเชื่อมต่อ
ทดสอบ Webhook
ประวัติส่ง Webhook
| เลขบันทึก | เลขรายการ | เหตุการณ์ | URL | สถานะ HTTP | ระยะเวลา (ms) | ข้อมูลตอบกลับ | วันเวลา | การดำเนินการ |
|---|---|---|---|---|---|---|---|---|
| ยังไม่มีประวัติส่งข้อมูลกลับ | ||||||||
คิวส่ง Webhook
| เลขงาน | เลขรายการ | ประเภทรายการ | จำนวน | สถานะ | จำนวนครั้ง | กำหนดตรวจซ้ำถัดไป | สาเหตุที่ไม่สำเร็จ | จัดการ |
|---|---|---|---|---|---|---|---|---|
| ไม่มีข้อมูลรอส่งซ้ำ | ||||||||