คู่มือ API
เชื่อมต่อการตรวจสลิปโอนเงินเข้ากับเว็บไซต์ แอปพลิเคชัน บอทไลน์ หรือระบบหลังบ้านของคุณ — ส่งรูปสลิปเข้ามา ได้ผล JSON รูปแบบเดียวกันเสมอ
ภาพรวมและการเริ่มต้น#
API ของเราเป็น REST ส่งรูปด้วย multipart/form-data และตอบกลับเป็น JSON (UTF-8) ทุกคำขอต้องแนบ API key ผลลัพธ์หักโควต้าแพ็กเกจก่อน เมื่อโควต้าหมดจึงหักเครดิตสลิปละ 1 เครดิต — ใช้ชุดเดียวกับการตรวจผ่านหน้าเว็บ
| Base URL | ใช้ทำอะไร |
|---|---|
https://ppay-slip.com | ที่อยู่ของ API ทุก endpoint ด้านล่างต่อท้ายด้วย path ที่ระบุ |
- 1
สมัครสมาชิกและมีโควต้า/เครดิต — สมัครสมาชิก แล้วเลือกแพ็กเกจหรือทดลองใช้ฟรี (ถ้าเปิดให้)
- 2
สร้าง API key ที่เมนู "API สำหรับนักพัฒนา" (ขึ้นต้นด้วย
ppk_) — ระบบแสดงคีย์เต็มครั้งเดียวเท่านั้น - 3
ลองเรียก
POST /api/v1/verifyด้วยรูปสลิปจริง (ดูตัวอย่างหัวข้อ "ตรวจสลิปแบบรอผล") - 4
ถ้าส่งสลิปจำนวนมาก ใช้แบบคิว + Webhook เพื่อไม่ต้องรอผลทีละใบ
การยืนยันตัวตน (API key)#
ส่ง API key ใน header Authorization แบบ Bearer (หรือ X-API-Key ก็ได้):
Authorization: Bearer ppk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
รักษา API key ให้ปลอดภัย
- เรียก API จาก เซิร์ฟเวอร์ของคุณ เท่านั้น ห้ามใส่คีย์ในโค้ดเว็บ/แอปมือถือที่ผู้ใช้เปิดดูได้ (API นี้ไม่เปิด CORS จึงเรียกจากเบราว์เซอร์ไม่ได้อยู่แล้ว)
- เก็บคีย์ใน environment variable หรือ secret manager ไม่ commit ลง Git
- แยกคีย์ตามแอปพลิเคชัน หากรั่วไหลให้ เพิกถอน ในหน้า "API สำหรับนักพัฒนา" (มีผลทันที) แล้วสร้างใหม่
- สร้างได้สูงสุด 10 คีย์ต่อบัญชี
คีย์ที่ไม่ถูกต้อง ถูกเพิกถอน หรือบัญชีถูกระงับ จะได้ 401 รหัส INVALID_API_KEY
ตรวจสลิปแบบรอผล#
POST https://ppay-slip.com/api/v1/verify — ส่งรูป 1 ใบ รอจนได้ผลแล้วตอบ HTTP 200 (โดยปกติใช้เวลาไม่กี่วินาที)
| ฟิลด์ (multipart) | จำเป็น | รายละเอียด |
|---|---|---|
file | ใช่ | รูปสลิป PNG หรือ JPG ขนาดไม่เกิน 5MB — ใช้รูปต้นฉบับจากแอปธนาคาร (ภาพที่ถูกครอปหรือบีบอัดมากอาจอ่าน QR ไม่ได้) |
queue | ไม่ | ใส่ never เพื่อบังคับให้ตอบผลตรง ๆ เสมอ (ดูหมายเหตุ 202 ด้านล่าง) |
curl -X POST https://ppay-slip.com/api/v1/verify \ -H "Authorization: Bearer ppk_xxxxxxxx" \ -F "file=@slip.jpg"
HTTP 200 ไม่ได้แปลว่าสลิปถูกต้อง
HTTP 200 แปลว่า "ตรวจเสร็จแล้ว" ให้ดูฟิลด์ valid / code เพื่อรู้ผล เช่นสลิปปลอมหรือสลิปซ้ำก็ตอบ 200 แต่ valid: false
ช่วงที่ระบบหนาแน่น อาจตอบ 202
เมื่อมีงานค้างจำนวนมาก หรือผู้ให้บริการตรวจสลิปขัดข้องชั่วคราว ระบบจะรับสลิปเข้าคิวให้เอง แล้วตอบ HTTP 202 พร้อม queued: true และ jobId(หักโควต้า/เครดิตครั้งเดียว) จากนั้นรับผลทาง Webhook หรือดึงด้วย GET /api/v1/jobs/{jobId}โค้ดของคุณจึงควรรองรับทั้ง 200 และ 202 เสมอ
{
"ok": true,
"queued": true,
"jobId": "job_1f3a9c...",
"status": "queued",
"reference": null,
"statusUrl": "/api/v1/jobs/job_1f3a9c..."
}ตรวจสลิปแบบคิว (async / batch)#
เหมาะกับระบบที่ส่งสลิปจำนวนมาก หรือไม่อยากค้างรอผล — ระบบรับงานเข้าคิวแล้วตอบ HTTP 202 ทันที (โดยหักโควต้า/เครดิตตอนรับงาน) ประมวลผลเบื้องหลัง และส่งผลให้ทาง Webhook เมื่อเสร็จ
1) ส่งทีละใบ — POST /api/v1/verify/async
| ฟิลด์ | จำเป็น | รายละเอียด |
|---|---|---|
file | ใช่ | รูปสลิป PNG/JPG ไม่เกิน 5MB |
reference | ไม่ | เลขอ้างอิงของคุณ เช่น เลขออเดอร์ (1–64 ตัวอักษร A-Z a-z 0-9 . _ : -) ระบบส่งคืนให้ในผลลัพธ์และ Webhook เพื่อจับคู่กับรายการของคุณ |
curl -X POST https://ppay-slip.com/api/v1/verify/async \ -H "Authorization: Bearer ppk_xxxxxxxx" \ -F "file=@slip.jpg" -F "reference=ORDER-1001"
2) ส่งหลายใบในคำขอเดียว — POST /api/v1/verify/batch
ส่งได้สูงสุด 10 ไฟล์ (รวมไม่เกิน 25MB) โดยซ้ำฟิลด์ file และ (ถ้าต้องการ) ซ้ำ reference ตามลำดับเดียวกัน แต่ละใบเป็นงานแยกกัน มี jobId ของตัวเอง
curl -X POST https://ppay-slip.com/api/v1/verify/batch \ -H "Authorization: Bearer ppk_xxxxxxxx" \ -F "file=@a.jpg" -F "file=@b.jpg" -F "file=@c.jpg" \ -F "reference=A-1" -F "reference=A-2" -F "reference=A-3"
{
"ok": true,
"accepted": 2,
"rejected": 1,
"jobs": [
{ "index": 0, "ok": true, "jobId": "job_aa...", "reference": "A-1" },
{ "index": 1, "ok": false, "code": "INVALID_FILE", "error": "file must be a valid PNG/JPG up to 5MB" },
{ "index": 2, "ok": true, "jobId": "job_bb...", "reference": "A-3" }
]
}- ไฟล์ที่ไม่ถูกต้องไม่ถูกหักเงิน และไม่กระทบใบอื่นในชุดเดียวกัน
- ถ้าโควต้า/เครดิตหมดกลางชุด ใบที่เหลือจะถูกปฏิเสธด้วยรหัส
INSUFFICIENT_BALANCE(ใบก่อนหน้าที่รับแล้วยังคงถูกประมวลผลตามปกติ)
3) ดึงผลของงาน — GET /api/v1/jobs/{jobId}
curl https://ppay-slip.com/api/v1/jobs/job_1f3a9c... \ -H "Authorization: Bearer ppk_xxxxxxxx"
{
"ok": true,
"jobId": "job_1f3a9c...",
"reference": "ORDER-1001",
"status": "done", // queued | processing | done
"createdAt": "2026-10-12T07:31:58.120Z",
"finishedAt": "2026-10-12T07:32:00.904Z",
"result": { /* เมื่อ status = done: รูปแบบเดียวกับผลตรวจสลิป ดูหัวข้อถัดไป + jobId, reference */ }
}ขณะ queued / processing ฟิลด์ result เป็น null — แนะนำให้ poll ห่างกัน 2–5 วินาที หรือใช้ Webhook แทนการ poll อ่านงานได้เฉพาะเจ้าของ API key เท่านั้น (งานของผู้อื่นจะได้ 404 JOB_NOT_FOUND)
ถ้าระบบตรวจสอบขัดข้อง คุณไม่ต้องทำอะไร
SERVICE_UNAVAILABLE และ คืนโควต้า/เครดิตให้ทันที(billing.refunded: true)รูปแบบผลลัพธ์#
ผลลัพธ์เป็นรูปแบบของเราเองเสมอ ไม่ว่าเบื้องหลังจะตรวจกับผู้ให้บริการรายใด ฟิลด์วันเวลาเป็น ISO-8601 (UTC) ยอดเงินเป็นบาท
{
"ok": true,
"id": "b1c2d3e4-...",
"valid": true,
"result": "valid",
"code": "VALID",
"message": "สลิปถูกต้อง",
"messageEn": "The slip is valid.",
"slip": {
"reference": "0158...9421",
"amount": 1250.00,
"currency": "THB",
"transferredAt": "2026-10-12T07:32:00.000Z",
"sender": { "name": "นาย สมชาย ใจดี", "bank": "ธนาคารกสิกรไทย" },
"receiver": { "name": "ร้านตัวอย่าง", "bank": "ธนาคารไทยพาณิชย์", "account": "xxx-x-x1234-x" }
},
"billing": { "paidWith": "quota", "refunded": false, "creditsLeft": 120, "quotaLeft": 480 }
}| ฟิลด์ | ความหมาย |
|---|---|
valid | true = สลิปถูกต้อง (code เป็น VALID) |
result | valid ถูกต้อง · invalid สลิปไม่ผ่าน (ปลอม/ซ้ำ/ไม่พบ) · error ระบบขัดข้อง (ไม่ถูกหักเงิน) |
code | รหัสผลลัพธ์ของเรา (ตารางถัดไป) — ใช้ในโค้ดของคุณ ไม่ควรเทียบกับข้อความ |
message / messageEn | ข้อความอธิบายภาษาไทย / อังกฤษ แสดงให้ผู้ใช้ปลายทางได้ |
slip | รายละเอียดสลิป (null เมื่อสลิปไม่ถูกต้อง): reference เลขอ้างอิงธนาคาร, amount, transferredAt, ผู้โอน, ผู้รับ (เลขบัญชีมักถูกปิดบางส่วน) |
cached | true = ตอบจากบันทึกของเราโดยไม่ต้องตรวจกับธนาคารซ้ำ (เคยตรวจสลิปไฟล์เดียวกันนี้แล้ว) — ยังถูกหักโควต้า/เครดิตตามปกติ |
previous | เมื่อส่งสลิปที่ บัญชีคุณเคยตรวจแล้ว: checkedAt เวลาที่ตรวจครั้งแรก และ slip จะมีรายละเอียดเดิมให้ แต่ valid เป็น false และ code เป็น DUPLICATE_SLIP (สลิปของบัญชีอื่นจะไม่เห็นรายละเอียด) |
billing.paidWith | quota หักจากโควต้าแพ็กเกจ · credit หักจากเครดิต · unlimited บัญชีผู้ดูแลระบบ (ไม่หัก) |
billing.refunded | true เมื่อระบบขัดข้องและคืนโควต้า/เครดิตให้แล้ว |
billing.creditsLeft / quotaLeft | ยอดคงเหลือหลังทำรายการ |
อะไรที่ถูกหักเงิน?
result: "error" ที่ระบบคืนให้อัตโนมัติ การส่งสลิปใบเดิมซ้ำจะได้ DUPLICATE_SLIP และถูกหักเพิ่มอีกครั้ง (ระบบตอบจากข้อมูลที่บันทึกไว้ได้ทันที จึงเร็วกว่าปกติ)รหัสผลลัพธ์ (code)#
| code | result | ความหมาย | ข้อความ (ไทย) |
|---|---|---|---|
| VALID | valid | สลิปถูกต้อง ยืนยันกับระบบธนาคารแล้ว | สลิปถูกต้อง |
| INVALID_SLIP | invalid | สลิปไม่ถูกต้อง หรือเป็นสลิปปลอม/ตัดต่อ | สลิปไม่ถูกต้อง หรือเป็นสลิปปลอม |
| SLIP_NOT_FOUND | invalid | ไม่พบรายการในระบบธนาคาร — อาจเพิ่งโอน (ลองใหม่ใน 1–2 นาที) หรืออ่าน QR ไม่ได้ | ไม่พบรายการนี้ในระบบธนาคาร (อาจเพิ่งโอน ลองใหม่ใน 1-2 นาที) หรืออ่าน QR บนสลิปไม่ได้ |
| DUPLICATE_SLIP | invalid | สลิปใบนี้เคยถูกตรวจ/ใช้ไปแล้ว | สลิปนี้ถูกใช้ตรวจสอบไปแล้ว |
| SERVICE_UNAVAILABLE | error | ผู้ให้บริการตรวจสลิปขัดข้อง — คืนโควต้า/เครดิตแล้ว ลองใหม่ภายหลัง | ระบบตรวจสอบสลิปขัดข้องชั่วคราว กรุณาลองใหม่ |
| NOT_CONFIGURED | error | ฝั่งผู้ดูแลระบบยังไม่ได้ตั้งค่าผู้ให้บริการตรวจสลิป — คืนโควต้า/เครดิตแล้ว | ระบบตรวจสอบสลิปยังไม่พร้อมใช้งาน |
| INTERNAL_ERROR | error | ข้อผิดพลาดภายใน — คืนโควต้า/เครดิตแล้ว | ระบบขัดข้อง กรุณาลองใหม่อีกครั้ง |
ข้อผิดพลาดของคำขอ#
เมื่อคำขอไม่สามารถรับได้ จะตอบ HTTP status ที่ไม่ใช่ 2xx พร้อม { "ok": false, "code": "...", "error": "..." } (ไม่มีการหักโควต้า/เครดิต)
| HTTP | code | สาเหตุ / วิธีแก้ |
|---|---|---|
| 400 | INVALID_FILE | ไม่มีไฟล์ หรือไม่ใช่รูป PNG/JPG ที่ถูกต้อง (ตรวจจากเนื้อไฟล์จริง ไม่ใช่นามสกุล) หรือใหญ่เกิน 5MB |
| 400 | INVALID_REFERENCE | reference ไม่ตรงรูปแบบ (1–64 ตัวอักษร A-Z a-z 0-9 . _ : -) |
| 400 | INVALID_FILE_COUNT | batch ต้องส่ง 1–10 ไฟล์ |
| 401 | INVALID_API_KEY | ไม่มีคีย์ คีย์ผิด/ถูกเพิกถอน หรือบัญชีถูกระงับ |
| 402 | INSUFFICIENT_BALANCE | โควต้าและเครดิตหมด — ซื้อแพ็กเกจหรือเติมเครดิต |
| 404 | JOB_NOT_FOUND | ไม่พบงานนี้ (หรือไม่ใช่งานของคีย์นี้ / งานเก่าเกิน 30 วันถูกลบแล้ว) |
| 413 | FILE_TOO_LARGE | คำขอใหญ่เกินกำหนด หรือไม่ได้ส่งเป็น multipart ปกติ (ต้องมี Content-Length) |
| 429 | RATE_LIMITED | เรียกถี่เกินไป — รอตามค่า header Retry-After (วินาที) แล้วลองใหม่ |
| 429 | QUEUE_FULL | มีงานค้างในคิวของบัญชีครบ 100 งาน — รอให้ประมวลผลเสร็จก่อน |
| 503 | STORAGE_UNAVAILABLE / QUEUE_UNAVAILABLE | ระบบจัดเก็บไฟล์/คิวขัดข้องชั่วคราว (ไม่หักเงิน) — ลองใหม่ |
Webhook#
แทนที่จะ poll ผลของงานแบบคิว ให้ระบบ ส่งผลมาหาคุณ เมื่อตรวจเสร็จ ตั้งค่าที่เมนู "API สำหรับนักพัฒนา" → Webhook:
- 1
ใส่ URL ปลายทางของคุณ — ต้องเป็น https และเป็นโดเมนสาธารณะ (ไม่รับ localhost / IP ภายใน)
- 2
บันทึก แล้วคัดลอก คีย์ลายเซ็น (
whsec_...) เก็บไว้ — แสดงครั้งเดียว หากหายให้กด "สร้างคีย์ใหม่" - 3
กด "ส่งทดสอบ" เพื่อดูว่าเซิร์ฟเวอร์ของคุณรับและตรวจลายเซ็นได้
เหตุการณ์ที่ส่ง:
| type | เมื่อไหร่ |
|---|---|
verification.completed | งานแบบคิว (async / batch / ที่ระบบรับเข้าคิวเองตอนหนาแน่น) ตรวจเสร็จ — ทั้งผลถูกต้อง ไม่ถูกต้อง และข้อผิดพลาดที่คืนเงินแล้ว |
webhook.test | เมื่อกด “ส่งทดสอบ” |
POST /webhooks/ppay HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: PPaySlip-Webhook/1.0
X-PPay-Event: verification.completed
X-PPay-Delivery: evt_9b1c2d...
X-PPay-Attempt: 1
X-PPay-Signature: t=1760254320,v1=5d41402abc4b2a76b9719d911017c592...
{
"id": "evt_9b1c2d...",
"type": "verification.completed",
"apiVersion": "1",
"createdAt": "2026-10-12T07:32:00.904Z",
"data": {
"jobId": "job_1f3a9c...",
"reference": "ORDER-1001",
"ok": true,
"valid": true,
"result": "valid",
"code": "VALID",
"message": "สลิปถูกต้อง",
"slip": { "reference": "0158...9421", "amount": 1250.00, "currency": "THB", "...": "..." },
"billing": { "paidWith": "quota", "refunded": false, "creditsLeft": 120, "quotaLeft": 480 }
}
}เซิร์ฟเวอร์ของคุณต้องทำอะไร
- ตอบ HTTP 2xx ภายใน 8 วินาที เพื่อยืนยันว่าได้รับแล้ว (ควรตอบทันทีแล้วค่อยประมวลผลต่อเบื้องหลัง)
- ตรวจลายเซ็นทุกครั้งก่อนเชื่อถือข้อมูล (หัวข้อถัดไป)
- ถ้าไม่ตอบ 2xx (รวมถึง 3xx — ระบบไม่ตามการ redirect) จะถือว่าล้มเหลวและส่งซ้ำ
การส่งซ้ำ (retry)
| ครั้งที่ล้มเหลว | ส่งซ้ำอีกครั้งใน |
|---|---|
| 1 | 1 นาที |
| 2 | 5 นาที |
| 3 | 30 นาที |
| 4 | 2 ชั่วโมง |
| 5 | 6 ชั่วโมง |
| 6 | เลิกส่งอัตโนมัติ (สถานะ “ล้มเหลว”) — กด “ส่งซ้ำ” เองได้ในหน้า API สำหรับนักพัฒนา |
อาจได้รับเหตุการณ์เดิมซ้ำ
id ของเหตุการณ์ (หรือ data.jobId) เก็บว่าประมวลผลไปแล้วหรือยัง เพื่อไม่ให้ทำรายการซ้ำ (เช่น ไม่ปรับสถานะออเดอร์ซ้ำ)ตรวจลายเซ็น Webhook#
ทุกคำขอมี header X-PPay-Signature: t=<unix seconds>,v1=<hex> โดย v1 คือ HMAC-SHA256 ของข้อความ "<t>.<raw body>" ด้วยคีย์ลายเซ็น (whsec_...) ของคุณ
สำคัญ
- ต้องใช้ raw body ตามที่ได้รับ (ก่อน parse JSON) เพราะแค่ช่องว่างต่างกันลายเซ็นก็ไม่ตรง
- เทียบด้วยฟังก์ชัน constant-time (
timingSafeEqual/hash_equals/compare_digest) - ปฏิเสธคำขอที่
tต่างจากเวลาปัจจุบันเกิน 5 นาที เพื่อกันการนำคำขอเก่ามาส่งซ้ำ (replay)
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.PPAY_WEBHOOK_SECRET; // whsec_...
const app = express();
function verify(raw, header, secret) {
const parts = Object.fromEntries(String(header).split(",").map((p) => p.split("=")));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // replay protection
const expected = createHmac("sha256", secret).update(parts.t + "." + raw).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1);
return a.length === b.length && timingSafeEqual(a, b);
}
// express.raw keeps the body exactly as received
app.post("/webhooks/ppay", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body.toString("utf8"), req.get("x-ppay-signature"), SECRET)) return res.sendStatus(400);
const event = JSON.parse(req.body.toString("utf8"));
res.sendStatus(200); // acknowledge first, then work
if (event.type === "verification.completed") {
// event.data.reference = your order number, event.data.valid = true/false
}
});<?php
$secret = getenv("PPAY_WEBHOOK_SECRET");
$raw = file_get_contents("php://input");
$header = $_SERVER["HTTP_X_PPAY_SIGNATURE"] ?? "";
parse_str(str_replace(",", "&", $header), $p); // t=...&v1=...
$expected = hash_hmac("sha256", ($p["t"] ?? "") . "." . $raw, $secret);
if (!isset($p["t"], $p["v1"]) || !hash_equals($expected, $p["v1"]) || abs(time() - (int)$p["t"]) > 300) {
http_response_code(400);
exit;
}
http_response_code(200);
$event = json_decode($raw, true);import hmac, hashlib, time, os
from flask import Flask, request, abort
SECRET = os.environ["PPAY_WEBHOOK_SECRET"]
app = Flask(__name__)
def verify(raw: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", "")) and abs(time.time() - int(parts["t"])) <= 300
@app.post("/webhooks/ppay")
def hook():
raw = request.get_data() # raw bytes, before JSON parsing
if not verify(raw, request.headers.get("X-PPay-Signature", ""), SECRET):
abort(400)
event = request.get_json()
return "", 200ตัวอย่างโค้ด#
Node.js 18+ — ตรวจแบบรอผล (รองรับ 202)
import { readFile } from "node:fs/promises";
async function verifySlip(path) {
const form = new FormData();
form.append("file", new Blob([await readFile(path)], { type: "image/jpeg" }), "slip.jpg");
const res = await fetch("https://ppay-slip.com/api/v1/verify", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.PPAY_API_KEY },
body: form,
});
const data = await res.json();
if (res.status === 202) return { pending: true, jobId: data.jobId }; // busy: wait for the webhook / poll the job
if (!res.ok) throw new Error(data.code + ": " + data.error); // 401, 402, 429 ...
return { pending: false, valid: data.valid, code: data.code, slip: data.slip };
}Node.js — ส่งเข้าคิวพร้อมเลขออเดอร์ แล้วรอ Webhook
async function queueSlip(path, orderNo) {
const form = new FormData();
form.append("file", new Blob([await readFile(path)], { type: "image/jpeg" }), "slip.jpg");
form.append("reference", orderNo); // comes back in the webhook
const res = await fetch("https://ppay-slip.com/api/v1/verify/async", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.PPAY_API_KEY },
body: form,
});
if (res.status !== 202) throw new Error((await res.json()).code);
return (await res.json()).jobId; // store it next to the order
}PHP (cURL)
<?php
$ch = curl_init("https://ppay-slip.com/api/v1/verify");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("PPAY_API_KEY")],
CURLOPT_POSTFIELDS => ["file" => new CURLFile("slip.jpg", "image/jpeg", "slip.jpg")],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$data = json_decode($body, true);
if ($status === 202) { /* queued: use $data["jobId"] */ }
elseif ($status === 200 && $data["valid"]) { echo $data["slip"]["amount"]; }Python (requests)
import os, requests
r = requests.post(
"https://ppay-slip.com/api/v1/verify",
headers={"Authorization": "Bearer " + os.environ["PPAY_API_KEY"]},
files={"file": ("slip.jpg", open("slip.jpg", "rb"), "image/jpeg")},
timeout=30,
)
data = r.json()
if r.status_code == 202:
job_id = data["jobId"] # queued
elif r.ok and data["valid"]:
print(data["slip"]["amount"])ผูกการตรวจสลิปกับร้านค้า (shop)#
ถ้าคุณสร้างร้านค้าและผูกบัญชีรับเงินไว้ในเมนู ร้านค้า & รับชำระเงิน คุณเลือกได้ว่าจะให้ระบบตรวจ ผู้รับในสลิป (เลขบัญชีและชื่อบัญชีที่ผูกไว้) หรือไม่ — ส่งฟิลด์ shop (รหัสร้าน หรือที่อยู่ร้าน) พร้อมรูปสลิป ใช้ได้กับ /api/v1/verify, /verify/async และ /verify/batch
| การเรียก | ผลที่ได้ |
|---|---|
| ไม่ส่ง shop | ตรวจสลิปทั่วไป: ยืนยันกับธนาคารว่าสลิปเป็นของจริงและไม่ซ้ำ โดยไม่สนว่าโอนเข้าใคร (เหมือนเดิมทุกประการ) |
shop=my-shop | ตรวจเหมือนข้างต้น และ ต้องโอนเข้าบัญชีใดบัญชีหนึ่งของร้านนั้น ถ้าไม่ตรงจะได้ valid: false และ code: RECEIVER_MISMATCH |
curl -X POST https://ppay-slip.com/api/v1/verify \ -H "Authorization: Bearer ppk_xxxxxxxx" \ -F "file=@slip.jpg" \ -F "shop=my-shop"
"shop": {
"id": "7c1f...",
"name": "ร้านของฉัน",
"matched": true, // true = โอนเข้าบัญชีของร้าน · false = โอนเข้าที่อื่น · null = ยังบอกไม่ได้
"account": { "id": "a91c...", "label": "ธนาคารกสิกรไทย" }
}- บัญชีของร้านที่กรอก “ชื่อบัญชี” ไว้ จะถูกเทียบทั้งเลขบัญชี (ตัวเลขที่มองเห็นบนสลิป) และชื่อผู้รับ ถ้าไม่กรอกชื่อ จะเทียบเลขบัญชีอย่างเดียว
- สลิปที่ผู้รับไม่ตรงจะ ไม่ถูกบันทึกว่าใช้แล้ว จึงนำไปตรวจกับร้านอื่นได้ แต่ยังนับเป็นการตรวจ 1 ครั้ง
- ข้อผิดพลาดก่อนหักเงิน:
400SHOP_NOT_FOUND(ไม่มีร้านนี้ในบัญชีคุณ) หรือSHOP_NO_ACCOUNT(ร้านยังไม่ได้ผูกบัญชีรับเงิน) - หน้าเว็บ “ตรวจสอบสลิป” มีตัวเลือกร้านเช่นเดียวกัน (เลือก “ไม่ผูกกับร้าน” เพื่อตรวจทั่วไป)
ใบแจ้งชำระเงิน (invoices) และ Webhook invoice.paid#
สร้างรายการรับชำระให้ลูกค้าของคุณจากระบบของคุณเอง แล้วส่งลิงก์ payUrl ให้ลูกค้า — ลูกค้าสแกน QR พร้อมเพย์ที่มียอดเงินอยู่แล้ว โอนเงิน และอัปโหลดสลิปที่หน้านั้น ระบบตรวจสลิปให้ (ผู้รับต้องเป็นบัญชีของร้าน ยอดต้องไม่ต่ำกว่ายอดรายการ และสลิปต้องทำรายการหลังสร้างรายการ) แล้วปิดรายการเป็น paid ให้อัตโนมัติ
| Endpoint | ใช้ทำอะไร |
|---|---|
POST /api/v1/invoices | สร้างรายการ body: shop, amount (1–1,000,000 ทศนิยมไม่เกิน 2 ตำแหน่ง), title, reference (เลขที่คำสั่งซื้อของคุณ), expiresInHours (1–720, ค่าเริ่มต้น 24) — ไม่หักโควต้า |
GET /api/v1/invoices/{code} | ดูสถานะรายการ (pending / paid / expired / cancelled) |
GET /api/v1/invoices?shop=&status= | รายการล่าสุด 100 รายการ กรองตามร้าน/สถานะได้ |
curl -X POST https://ppay-slip.com/api/v1/invoices \
-H "Authorization: Bearer ppk_xxxxxxxx" -H "Content-Type: application/json" \
-d '{"shop":"my-shop","amount":590,"reference":"ORDER-1001","title":"สั่งซื้อสินค้า"}'{
"ok": true,
"invoice": {
"code": "k3n8xq2m7vbt4h9d",
"status": "pending",
"amount": 590,
"currency": "THB",
"reference": "ORDER-1001",
"payUrl": "https://ppay-slip.com/pay/k3n8xq2m7vbt4h9d",
"expiresAt": "2026-10-13T08:00:00.000Z",
"paidAt": null
}
}เมื่อลูกค้าชำระสำเร็จ ระบบส่ง Webhook (ถ้าคุณตั้งไว้) เหตุการณ์ invoice.paid พร้อมลายเซ็นเหมือนเหตุการณ์อื่น:
{
"id": "evt_...", "type": "invoice.paid", "apiVersion": "1", "createdAt": "...",
"data": {
"invoice": { "code": "k3n8xq2m7vbt4h9d", "reference": "ORDER-1001", "amount": 590, "paidAt": "..." },
"shop": { "id": "7c1f...", "name": "ร้านของฉัน" },
"slip": { "reference": "0158...", "amount": 590, "transferredAt": "...", "sender": {...}, "receiver": {...} }
}
}ค่าตรวจสลิปของรายการรับชำระ
สร้าง QR พร้อมเพย์#
GET|POST https://ppay-slip.com/api/v1/promptpay — ใส่เบอร์พร้อมเพย์ (หรือเลขบัตรประชาชน/ผู้เสียภาษี) และจำนวนเงิน แล้วได้ QR มาตรฐาน PromptPay ให้ลูกค้าสแกนชำระ ไม่หักโควต้าหรือเครดิต แต่จำกัด 120 ครั้ง/นาที ต่อบัญชี
| พารามิเตอร์ | จำเป็น | รายละเอียด |
|---|---|---|
target | ใช่ | เบอร์มือถือ 10 หลักที่ขึ้นต้น 06/08/09 (ใส่ - หรือ +66 ได้) หรือเลข 13 หลัก (บัตรประชาชน/ผู้เสียภาษี) หรือ e-Wallet 15 หลัก |
amount | ไม่ | จำนวนเงินบาท 0.01 – 1,000,000 ทศนิยมไม่เกิน 2 ตำแหน่ง ถ้าไม่ส่ง = QR ที่ให้ผู้จ่ายกรอกยอดเอง |
format | ไม่ | `json` (ค่าเริ่มต้น) หรือ svg เพื่อรับรูป QR กลับมาตรง ๆ |
curl -X POST https://ppay-slip.com/api/v1/promptpay \
-H "Authorization: Bearer ppk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"target":"081-234-5678","amount":100.50}'{
"ok": true,
"payload": "00020101021229370016A0000006770101110113006681234567853037645406100.505802TH6304XXXX",
"amount": 100.5,
"currency": "THB",
"target": { "type": "phone", "masked": "081xxxx678" },
"qr": { "svg": "<svg ...>", "dataUrl": "data:image/svg+xml;base64,..." }
}qr.dataUrlใส่ใน<img src>ได้ทันที หรือใช้payloadสร้าง QR ด้วยไลบรารีของคุณเอง- ข้อผิดพลาด:
400รหัสINVALID_TARGET/INVALID_AMOUNT/INVALID_FORMAT - QR นี้เป็นเพียงข้อมูลสำหรับสแกน ระบบไม่ได้ตรวจว่าลูกค้าชำระแล้ว — ให้ลูกค้าส่งสลิปมาตรวจด้วย
/api/v1/verify
ข้อจำกัดและโควต้า#
| รายการ | ค่า |
|---|---|
| ขนาดรูป | ไม่เกิน 5MB ต่อใบ — เฉพาะ PNG / JPG |
| ความถี่การเรียก | 60 ใบ/นาที ต่อบัญชี (รวม verify, async และ batch) — เกินจะได้ 429 พร้อม Retry-After |
| batch | สูงสุด 10 ไฟล์ และรวมไม่เกิน 25MB ต่อคำขอ |
| งานค้างในคิว | สูงสุด 100 งานต่อบัญชี (queued + processing) |
| ความหนาแน่น | ระบบสลับเป็นเข้าคิวเองเมื่อมีงานค้างตั้งแต่ 30 งานขึ้นไปทั้งระบบ หรือผู้ให้บริการตรวจสลิปทุกรายขัดข้อง |
| ลองใหม่ของงานคิว | สูงสุด 3 ครั้งเมื่อผู้ให้บริการขัดข้อง (รอ 30 วินาที แล้ว 2 นาที) จากนั้นคืนโควต้า/เครดิต |
| เก็บผลงานคิว | 30 วัน (ประวัติ Webhook 14 วัน) |
| Webhook | ตอบ 2xx ภายใน 8 วินาที · ส่งซ้ำสูงสุด 6 ครั้งรวม · URL ต้องเป็น https สาธารณะ |
| API key | ได้สูงสุด 10 คีย์ต่อบัญชี |
| ค่าตรวจ | โควต้าแพ็กเกจก่อน เมื่อหมดหักเครดิต 1 เครดิต/ใบ |
แนวทางที่แนะนำ#
- จับคู่กับออเดอร์ด้วย reference — ส่ง
reference(เลขออเดอร์) ทุกครั้งที่ใช้แบบคิว แล้วใช้ค่านี้ใน Webhook เพื่ออัปเดตออเดอร์ถูกรายการ - เทียบยอดเงินและผู้รับเอง — API ยืนยันว่าสลิปเป็นของจริงและยังไม่เคยใช้ แต่การตัดสินว่า "ยอด/บัญชีผู้รับถูกต้องตามออเดอร์หรือไม่" เป็นหน้าที่ของระบบคุณ ให้เทียบ
slip.amountและslip.receiverกับออเดอร์ก่อนอนุมัติ - รองรับ 202 เสมอ แม้ใช้ endpoint แบบรอผล เพราะช่วงหนาแน่นระบบอาจรับเข้าคิวให้
- ทำให้ตัวจัดการ Webhook เป็น idempotent — เก็บ
idเหตุการณ์ที่ประมวลผลแล้ว - อย่าลองซ้ำอัตโนมัติเมื่อได้ผล invalid — การส่งสลิปใบเดิมซ้ำให้ผล
DUPLICATE_SLIPและถูกหักเพิ่ม ให้ลองซ้ำเฉพาะเมื่อresult: "error"(คืนเงินแล้ว) หรือ HTTP 5xx/429 - เก็บ log ของ
id/jobIdที่ได้รับ เพื่อใช้ตรวจสอบย้อนหลังร่วมกับหน้า "ประวัติ" ในพื้นที่สมาชิก - ติดปัญหา? แจ้งทีมงานพร้อมแนบ
idหรือjobIdของรายการ
